<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Vue 源码解读 | Justic</title><meta name="keywords" content="Vue"><meta name="author" content="Justic"><meta name="copyright" content="Justic"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="一、Vue源码解析–响应式原理1、课程目标 Vue.js的静态成员和实例成员初始化过程 ​ 首次渲染的过程 数据响应式原理  2、准备工作Vue源码的获取 项目地址：https:&#x2F;&#x2F;github.com&#x2F;vuejs&#x2F;vue 为什么分析Vue2.6? 新的版本发布后，现有项目不会升级到3.0,2.x还有很长的一段过渡期。 3.0项目地址https:&#x2F;&#x2F;github.com&#x2F;vuejs&#x2F;vue-nex">
<meta property="og:type" content="article">
<meta property="og:title" content="Vue 源码解读">
<meta property="og:url" content="http://example.com/2022/07/15/Vue/Vue%20%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/index.html">
<meta property="og:site_name" content="Justic">
<meta property="og:description" content="一、Vue源码解析–响应式原理1、课程目标 Vue.js的静态成员和实例成员初始化过程 ​ 首次渲染的过程 数据响应式原理  2、准备工作Vue源码的获取 项目地址：https:&#x2F;&#x2F;github.com&#x2F;vuejs&#x2F;vue 为什么分析Vue2.6? 新的版本发布后，现有项目不会升级到3.0,2.x还有很长的一段过渡期。 3.0项目地址https:&#x2F;&#x2F;github.com&#x2F;vuejs&#x2F;vue-nex">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/j3/wallhaven-j3qq15.jpg">
<meta property="article:published_time" content="2022-07-15T12:05:37.000Z">
<meta property="article:modified_time" content="2022-07-15T13:33:14.625Z">
<meta property="article:author" content="Justic">
<meta property="article:tag" content="Vue">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/j3/wallhaven-j3qq15.jpg"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="http://example.com/2022/07/15/Vue/Vue%20%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Vue 源码解读',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-07-15 21:33:14'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><!-- hexo injector head_end start --><link rel="stylesheet" href="https://cdn.cbd.int/hexo-butterfly-clock-anzhiyu/lib/clock.min.css" /><!-- hexo injector head_end end --><meta name="generator" content="Hexo 6.2.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "" data-lazy-src="https://portrait.gitee.com/uploads/avatars/user/2982/8948190_JIAXInT_1624177607.png!avatar200" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">22</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">2</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">3</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fa fa-graduation-cap"></i><span> 博文</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw fa fa-archive"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw fa fa-folder-open"></i><span> 归档</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/project/"><i class="fa-fw fas fa-images"></i><span> 项目</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/messageboard/"><i class="fa-fw fa fa-paper-plane"></i><span> 留言板</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于笔者</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/j3/wallhaven-j3qq15.jpg')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">Justic</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fa fa-graduation-cap"></i><span> 博文</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw fa fa-archive"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw fa fa-folder-open"></i><span> 归档</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/project/"><i class="fa-fw fas fa-images"></i><span> 项目</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/messageboard/"><i class="fa-fw fa fa-paper-plane"></i><span> 留言板</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于笔者</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">Vue 源码解读</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-07-15T12:05:37.000Z" title="发表于 2022-07-15 20:05:37">2022-07-15</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-07-15T13:33:14.625Z" title="更新于 2022-07-15 21:33:14">2022-07-15</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Vue/">Vue</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">26.2k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>103分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="Vue 源码解读"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span><span class="post-meta-separator">|</span><span class="post-meta-commentcount"><i class="far fa-comments fa-fw post-meta-icon"></i><span class="post-meta-label">评论数:</span><a href="/2022/07/15/Vue/Vue%20%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/#post-comment" itemprop="discussionUrl"><span class="valine-comment-count" data-xid="/2022/07/15/Vue/Vue%20%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/" itemprop="commentCount"><i class="fa-solid fa-spinner fa-spin"></i></span></a></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h1 id="一、Vue源码解析–响应式原理"><a href="#一、Vue源码解析–响应式原理" class="headerlink" title="一、Vue源码解析–响应式原理"></a>一、<code>Vue</code>源码解析–响应式原理</h1><h1 id="1、课程目标"><a href="#1、课程目标" class="headerlink" title="1、课程目标"></a>1、课程目标</h1><ul>
<li><code>Vue.js</code>的静态成员和实例成员初始化过程</li>
<li>​ 首次渲染的过程</li>
<li>数据响应式原理</li>
</ul>
<h1 id="2、准备工作"><a href="#2、准备工作" class="headerlink" title="2、准备工作"></a>2、准备工作</h1><p><code>Vue</code>源码的获取</p>
<p>项目地址：<code>https://github.com/vuejs/vue</code></p>
<p>为什么分析<code>Vue2.6</code>? 新的版本发布后，现有项目不会升级到<code>3.0</code>,<code>2.x</code>还有很长的一段过渡期。</p>
<p><code>3.0</code>项目地址<code>https://github.com/vuejs/vue-next</code></p>
<p>源码目录结构(在<code>src</code>目录下面定义的就是源码内容)：</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">compiler: 编译相关（主要作用:就是把模板转换成render函数，在render函数中创建虚拟DOM）</span><br><span class="line">core:Vue核心库</span><br><span class="line">platforms:平台相关代码,web:基于web的开发，weex是基于移动端的开发</span><br><span class="line">server:SSR，服务端渲染</span><br><span class="line">sfc:将.vue文件编译为js对象</span><br><span class="line">shared:公共的代码</span><br></pre></td></tr></table></figure>

<p>​ 在<code>core</code>目录是<code>Vue</code>的核心库，在<code>core</code>目录下面，也定义了很多的文件夹，下面我们先简单来看一下。</p>
<p><code>components</code>目录下面定义的是<code>keep-alive.js</code>组件。</p>
<p><code>global-api</code>:定义的是<code>Vue</code>中的静态方法。<code>vue.filter</code>,<code>vue.extend</code>,<code>vue.mixin</code>,<code>vue.use</code>等。</p>
<p><code>Instance</code>:创建<code>vue</code>的实例，定义了<code>Vue</code>的构造函数，初始化，以及生命周期的钩子函数等。</p>
<p><code>observer</code>:定义响应式机制的位置，</p>
<p><code>util</code>:定义公共成员。</p>
<p><code>vodom</code>:定义虚拟<code>DOM</code></p>
<h1 id="3、打包"><a href="#3、打包" class="headerlink" title="3、打包"></a>3、打包</h1><p>这里我们来介绍一下，关于<code>Vue</code>源码中使用的打包方式。</p>
<p>打包工具<code>Rollup</code></p>
<p><code>Vue.js</code> 所使用的打包工具为<code>Rollup</code>,<code>Rollup</code>比<code>Webpack</code>更加轻量，<code>Webpack</code>是把所有的文件（例如：图片文件，样式等）当作模块进行打包，<code>Rollup</code>只处理<code>js</code>文件，所以<code>Rollup</code>更适合在<code>Vue.js</code>这样的库中进行使用。</p>
<p><code>Rollup</code>打包不会生成冗余的代码，如果是<code>Webpack</code>打包，那么会生成一些浏览器支持模块化的代码。</p>
<p>以上就是<code>Webpack</code>与<code>Rollup</code>之间的区别。根据以上的讲解，其实我们可以总结出，<code>Rollup</code>更适合在库的开发中使用，<code>Webpack</code>更适合在项目开发中使用，所以它们各自有自己的应用场景。</p>
<p>下面看一下打包的步骤:</p>
<p>第一步：安装依赖</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">npm i</span><br></pre></td></tr></table></figure>

<p>第二步设置:<code>sourcemap</code></p>
<p><code>sourcemap</code>是代码地图，在<code>sourcemap</code>中记录了打包后的代码与源码之间的对应关系。如果出错了，也会告诉我们源码中的第几行出错了。怎样设置<code>sourcemap</code>呢？在<code>package.json</code> 文件中的<code>dev</code>脚本中添加<code>--sourcemap</code>.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&quot;dev&quot;: &quot;rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev&quot;,</span><br></pre></td></tr></table></figure>

<p>第三步:执行<code>dev</code>,运行<code>npm run dev</code>执行打包，用的是<code>rollup</code>,<code>-w</code> 参数是监听源码文件的变化，源码文件变化后自动的重新进行打包。<code>-c</code>是设置配置文件，<code>scripts/config.js</code>就是配置文件,<code>environment</code>环境变量，通过后面设置的值，来打包生成不同版本的<code>Vue</code>.<code>web-full-dev</code>:<code>web</code>:指的是打包<code>web</code>平台下的，<code>full</code>:表示完整版，包含了编译器与运行时，<code>dev</code>:表示的是开发版本，不会对代码进行压缩。</p>
<p><code>web-runtime-cjs-dev</code>: <code>runtime</code>:表示运行时，<code>cjs</code>：表示<code>CommonJS</code>模块。</p>
<p>在执行<code>npm run dev</code>进行打包之前，可以先来看一下<code>dist</code>目录，该目录下面已经有很多的<code>js</code>文件，这些文件针对的是不同版本的<code>Vue</code>.那么为了更好的看到，执行<code>npm run dev</code>命令后的打包效果，在这里可以将这些文件先删除掉。</p>
<h1 id="4、Vue-不同版本说明"><a href="#4、Vue-不同版本说明" class="headerlink" title="4、Vue 不同版本说明"></a>4、Vue 不同版本说明</h1><p><code>https://cn.vuejs.org/v2/guide/installation.html#对不同构建版本的解释</code></p>
<p>完整版：同时包含<code>编译器</code>和<code>运行时</code>版本。</p>
<p>​ 什么是编译器？用来将模板字符串编译成为<code>javascript</code>渲染函数(<code>render</code>函数，<code>render</code>函数用来生成虚拟<code>DOM</code>)的代码，体积大，效率低。</p>
<p>​ 什么是运行时？用来创建<code>Vue</code>实例，渲染并处理虚拟<code>DOM</code>等的代码，体积小，效率高，基本上就是除去编译器的代码。</p>
<p>还有一点需要说明的是：<code>Vue</code>包含了不同的模块化方式。</p>
<p><code>UMD</code>:指的是通用的模块版本，支持多种模块方式，<code>UMD</code> 版本可以通过 <code>&lt;script&gt;</code> 标签直接用在浏览器中</p>
<p>**<a target="_blank" rel="noopener" href="http://wiki.commonjs.org/wiki/Modules/1.1">CommonJS</a>**：<code>CommonJS </code>版本用来配合老的打包工具比如<code> [Browserify</code>](<a target="_blank" rel="noopener" href="http://browserify.org/">http://browserify.org/</a>) 或<code> [webpack 1</code>](<a target="_blank" rel="noopener" href="https://webpack.github.io/">https://webpack.github.io/</a>)</p>
<p>**<a target="_blank" rel="noopener" href="http://exploringjs.com/es6/ch_modules.html"><code>ES Module</code></a>**：从 2.6 开始 <code>Vue </code>会提供两个 <code>ES Modules (ESM，也是ES6的模块化方式，这时标准的模块化方式，后期会使用该方式替换其它的模块化方式)</code> 构建文件：</p>
<ul>
<li>为打包工具提供的<code> ESM</code>：为诸如 <code>[webpack 2](https://webpack.js.org/)</code> 或 <code>[Rollup]</code>(<a target="_blank" rel="noopener" href="https://rollupjs.org/">https://rollupjs.org/</a>) 提供的现代打包工具。<code>ESM</code> 格式被设计为可以被静态分析(在编译的时候进行代码的处理也就是解析模块之间的依赖，而不是运行时)，所以打包工具可以利用这一点来进行“tree-shaking”并将用不到的代码排除出最终的包。为这些打包工具提供的默认文件 (<code>pkg.module</code>) 是只有运行时的 <code>ES Module</code> 构建 (<code>vue.runtime.esm.js</code>)。</li>
<li>为浏览器提供的 <code>ESM </code>(2.6+)：用于在现代浏览器中通过 <code>&lt;script type=&quot;module&quot;&gt;</code> 直接导入。</li>
</ul>
<p>如果使用<code>vue-cli</code>创建的项目，默认的就是运行时版本，并且使用的是<code>ES6</code>的模块化方式。</p>
<p>同时使用<code>vue-cli</code>创建的项目中，有很多的<code>.vue</code>文件，而这些文件浏览器是不支持的，所以在打包的时候，会将这些单文件转换成<code>js</code>对象，在转换<code>js</code>对象的过程中，会将<code>.vue</code>文件中的<code>template</code>转换成<code>render</code>函数。</p>
<p>所以单文件组件在运行的时候也是不需要编译器的。</p>
<h1 id="5、寻找入口文件"><a href="#5、寻找入口文件" class="headerlink" title="5、寻找入口文件"></a>5、寻找入口文件</h1><p>查看<code>vue</code>的源码，就需要找到对应的入口文件。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&quot;dev&quot;: &quot;rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev&quot;,</span><br></pre></td></tr></table></figure>

<p>可以从<code>srcipts/config.js</code>这个配置文件中进行查找。</p>
<p>该配置文件中的内容是比较多的。所以可以看一下文件的底部，底部导出了相应的内容、</p>
<p>如下所示：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//判断环境变量是否有`TARGET`</span></span><br><span class="line"><span class="comment">//如果有的话，使用`genConfig()`生成`rollup`配置文件。</span></span><br><span class="line"><span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">TARGET</span>) &#123;</span><br><span class="line">  <span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">genConfig</span>(process.<span class="property">env</span>.<span class="property">TARGET</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="built_in">exports</span>.<span class="property">getBuild</span> = genConfig;</span><br><span class="line">  <span class="built_in">exports</span>.<span class="property">getAllBuilds</span> = <span class="function">() =&gt;</span> <span class="title class_">Object</span>.<span class="title function_">keys</span>(builds).<span class="title function_">map</span>(genConfig);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面，我们看一下<code>genConfig</code>方法的代码实现</p>
<p>在<code>getConfig</code>方法中有一行代码如下所示：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> opts = builds[name];</span><br></pre></td></tr></table></figure>

<p>我们看到在<code>builds</code>这个对象中，该对象中的属性就是：环境变量的值。</p>
<p>由于在<code>package.json</code>文件中，关于<code>dev</code>的配置中的环境变量的值为<code>web-full-dev</code>.</p>
<p>所以下面，我们在<code>builds</code>对象中查找该属性对应的内容。</p>
<p>具体内容如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Runtime+compiler development build (Browser)</span></span><br><span class="line"> <span class="string">&#x27;web-full-dev&#x27;</span>: &#123;</span><br><span class="line">     <span class="comment">//表示入口文件，我们查找的就是该文件。</span></span><br><span class="line">   <span class="attr">entry</span>: <span class="title function_">resolve</span>(<span class="string">&#x27;web/entry-runtime-with-compiler.js&#x27;</span>),</span><br><span class="line">       <span class="comment">//出口，打包后的目标文件</span></span><br><span class="line">   <span class="attr">dest</span>: <span class="title function_">resolve</span>(<span class="string">&#x27;dist/vue.js&#x27;</span>),</span><br><span class="line">       <span class="comment">//模块化的方式，这里是umd</span></span><br><span class="line">   <span class="attr">format</span>: <span class="string">&#x27;umd&#x27;</span>,</span><br><span class="line">       <span class="comment">//打包方式，env的取值可以是开发模式或者是生产模式</span></span><br><span class="line">   <span class="attr">env</span>: <span class="string">&#x27;development&#x27;</span>,</span><br><span class="line">       <span class="comment">//别名，这里先不用关系</span></span><br><span class="line">   <span class="attr">alias</span>: &#123; <span class="attr">he</span>: <span class="string">&#x27;./entity-decoder&#x27;</span> &#125;,</span><br><span class="line">       <span class="comment">//表示的就是文件的头，打包好的文件的头部信息。</span></span><br><span class="line">   banner</span><br><span class="line"> &#125;,</span><br></pre></td></tr></table></figure>

<p>在<code>web-full-dev</code>中定义的就是在打包的时候，需要的一些配置的基本信息。</p>
<p>通过以上代码的注意，我们知道，<code>web-full-dev</code>打包的是完整版，包含了运行时与编译器。</p>
<p>下面我们来看一下，入口文件，入口文件的地址为<code>web/entry-runtime-with-compiler.js</code>, 但是问题是在<code>scripts</code>目录中，我们没有发现<code>web</code>目录，我们进入<code>reslove</code>方法看一下，</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> aliases = <span class="built_in">require</span>(<span class="string">&quot;./alias&quot;</span>); <span class="comment">//导入alias模块</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">resolve</span> = (<span class="params">p</span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">//根据传递过来的参数，安装`/`进行分隔，然后获取第一项内容。</span></span><br><span class="line">  <span class="comment">//很明显这里获取的是 web</span></span><br><span class="line">  <span class="keyword">const</span> base = p.<span class="title function_">split</span>(<span class="string">&quot;/&quot;</span>)[<span class="number">0</span>];</span><br><span class="line">  <span class="comment">//根据获取到的`web`,从aliases中获取一个值，下面看一下aliases中的内容。</span></span><br><span class="line">  <span class="keyword">if</span> (aliases[base]) &#123;</span><br><span class="line">    <span class="comment">//aliases[base]的值:src/platforms/web</span></span><br><span class="line">    <span class="comment">// p的值为：web/entry-runtime-with-compiler.js</span></span><br><span class="line">    <span class="comment">//p.slice(base.length + 1)：获取到的就是entry-runtime-with-compiler.js</span></span><br><span class="line">    <span class="comment">//整个返回的内容是：src/platforms/web/entry-runtime-with-compiler.js 的绝对路径并返回</span></span><br><span class="line">    <span class="keyword">return</span> path.<span class="title function_">resolve</span>(aliases[base], p.<span class="title function_">slice</span>(base.<span class="property">length</span> + <span class="number">1</span>));</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;../&quot;</span>, p);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p><code>aliases</code>中的内容定义在<code>scripts/alias.js</code>文件，具体的代码如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">resolve</span> = (<span class="params">p</span>) =&gt; path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;../&quot;</span>, p);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">vue</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/platforms/web/entry-runtime-with-compiler&quot;</span>),</span><br><span class="line">  <span class="attr">compiler</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/compiler&quot;</span>),</span><br><span class="line">  <span class="attr">core</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/core&quot;</span>),</span><br><span class="line">  <span class="attr">shared</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/shared&quot;</span>),</span><br><span class="line">  <span class="attr">web</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/platforms/web&quot;</span>),</span><br><span class="line">  <span class="attr">weex</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/platforms/weex&quot;</span>),</span><br><span class="line">  <span class="attr">server</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/server&quot;</span>),</span><br><span class="line">  <span class="attr">sfc</span>: <span class="title function_">resolve</span>(<span class="string">&quot;src/sfc&quot;</span>),</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>通过上面的代码，我们可以看到这里是通过<code>path.resolve</code>获取到了当前的绝对路径，并且是在<code>scripts</code>目录的上一级<code>src</code>下面去查找<code>platforms/web</code>目录中的内容。</p>
<p>下面我们继续来看一下<code>genConfig</code>方法。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">genConfig</span>(<span class="params">name</span>) &#123;</span><br><span class="line">  <span class="comment">//获取到了关于配置的基础信息</span></span><br><span class="line">  <span class="keyword">const</span> opts = builds[name];</span><br><span class="line">  <span class="comment">//config对象就是所有的配置信息</span></span><br><span class="line">  <span class="keyword">const</span> config = &#123;</span><br><span class="line">    <span class="attr">input</span>: opts.<span class="property">entry</span>, <span class="comment">//入口</span></span><br><span class="line">    <span class="attr">external</span>: opts.<span class="property">external</span>,</span><br><span class="line">    <span class="attr">plugins</span>: [<span class="title function_">flow</span>(), <span class="title function_">alias</span>(<span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, aliases, opts.<span class="property">alias</span>))].<span class="title function_">concat</span>(</span><br><span class="line">      opts.<span class="property">plugins</span> || []</span><br><span class="line">    ),</span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">      <span class="attr">file</span>: opts.<span class="property">dest</span>, <span class="comment">//出口</span></span><br><span class="line">      <span class="attr">format</span>: opts.<span class="property">format</span>,</span><br><span class="line">      <span class="attr">banner</span>: opts.<span class="property">banner</span>,</span><br><span class="line">      <span class="attr">name</span>: opts.<span class="property">moduleName</span> || <span class="string">&quot;Vue&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">onwarn</span>: <span class="function">(<span class="params">msg, warn</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (!<span class="regexp">/Circular/</span>.<span class="title function_">test</span>(msg)) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(msg);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// built-in vars</span></span><br><span class="line">  <span class="keyword">const</span> vars = &#123;</span><br><span class="line">    <span class="attr">__WEEX__</span>: !!opts.<span class="property">weex</span>,</span><br><span class="line">    <span class="attr">__WEEX_VERSION__</span>: weexVersion,</span><br><span class="line">    <span class="attr">__VERSION__</span>: version,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">// feature flags</span></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">keys</span>(featureFlags).<span class="title function_">forEach</span>(<span class="function">(<span class="params">key</span>) =&gt;</span> &#123;</span><br><span class="line">    vars[<span class="string">`process.env.<span class="subst">$&#123;key&#125;</span>`</span>] = featureFlags[key];</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="comment">// build-specific env</span></span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">env</span>) &#123;</span><br><span class="line">    vars[<span class="string">&quot;process.env.NODE_ENV&quot;</span>] = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(opts.<span class="property">env</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  config.<span class="property">plugins</span>.<span class="title function_">push</span>(<span class="title function_">replace</span>(vars));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">transpile</span> !== <span class="literal">false</span>) &#123;</span><br><span class="line">    config.<span class="property">plugins</span>.<span class="title function_">push</span>(<span class="title function_">buble</span>());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(config, <span class="string">&quot;_name&quot;</span>, &#123;</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">value</span>: name,</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="comment">//将配置信息返回</span></span><br><span class="line">  <span class="keyword">return</span> config;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="6、从入口开始"><a href="#6、从入口开始" class="headerlink" title="6、从入口开始"></a>6、从入口开始</h1><p>通过上一小节的内容，我们已经找到了对应的入口文件<code>src/platform/web/entry-runtime-with-compiler.js</code></p>
<p>下面我们要对入口文件进行分析，在分析的过程中，我们要解决一个问题，如下代码所示：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> vm=<span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>:<span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line"><span class="attr">template</span>:<span class="string">&#x27;&lt;h3&gt;hello template&lt;/h3&gt;&#x27;</span></span><br><span class="line"><span class="title function_">render</span>(<span class="params">h</span>)&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">h</span>(<span class="string">&#x27;h4&#x27;</span>,<span class="string">&#x27;hello render&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>在上面的代码中，我们在创建<code>Vue</code>的实例的时候，同时指定了<code>template</code>与<code>render</code>，那么会渲染执行哪个内容？</p>
<p>一会我们通过查看源码来解决这个问题。</p>
<p>下面我们打开入口文件。</p>
<p>先来看一下<code>$mount</code></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//保留Vue实例的$mount方法</span></span><br><span class="line"><span class="keyword">const</span> mount = <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span>;</span><br><span class="line"><span class="comment">//$mout:挂载，作用就是把生成的DOM挂载到页面中。</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span> = <span class="keyword">function</span> (<span class="params"></span></span><br><span class="line"><span class="params">  el?: string | Element,</span></span><br><span class="line"><span class="params">  <span class="comment">//非ssr情况下为false,ssr的时候为true</span></span></span><br><span class="line"><span class="params">  hydrating?: boolean</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Component</span> &#123;</span><br><span class="line">  <span class="comment">//获取el选项，创建vue实例的时候传递过来的选项。</span></span><br><span class="line">  <span class="comment">//el就是DOM对象。</span></span><br><span class="line">  el = el &amp;&amp; <span class="title function_">query</span>(el);</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">  <span class="comment">//如果el为body或者是html,并且是开发环境，那么会在浏览器的控制台</span></span><br><span class="line">  <span class="comment">//中输出不能将Vue的实例挂载到&lt;html&gt;或者是&lt;body&gt;标签上</span></span><br><span class="line">  <span class="keyword">if</span> (el === <span class="variable language_">document</span>.<span class="property">body</span> || el === <span class="variable language_">document</span>.<span class="property">documentElement</span>) &#123;</span><br><span class="line">    process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp;</span><br><span class="line">      <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">`Do not mount Vue to &lt;html&gt; or &lt;body&gt; - mount to normal elements instead.`</span></span><br><span class="line">      );</span><br><span class="line">    <span class="comment">//直接返回vue的实例</span></span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//获取options选项</span></span><br><span class="line">  <span class="keyword">const</span> options = <span class="variable language_">this</span>.<span class="property">$options</span>;</span><br><span class="line">  <span class="comment">// resolve template/el and convert to render function</span></span><br><span class="line">  <span class="comment">//判断options中是否有render(在创建vue实例的时候，也就new Vue的时候是否传递了render函数)</span></span><br><span class="line">  <span class="keyword">if</span> (!options.<span class="property">render</span>) &#123;</span><br><span class="line">    <span class="comment">//没有传递render函数。获取template模板，然后将其转换成render函数</span></span><br><span class="line">    <span class="comment">//关于将`template`转换成render的代码比较多，目录先知道其主要作用就可以了</span></span><br><span class="line">    <span class="keyword">let</span> template = options.<span class="property">template</span>;</span><br><span class="line">    <span class="keyword">if</span> (template) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> template === <span class="string">&quot;string&quot;</span>) &#123;</span><br><span class="line">        <span class="comment">//如果是id选择器</span></span><br><span class="line">        <span class="keyword">if</span> (template.<span class="title function_">charAt</span>(<span class="number">0</span>) === <span class="string">&quot;#&quot;</span>) &#123;</span><br><span class="line">          <span class="comment">//获取对应的DOM对象的innerHTML,作为模板</span></span><br><span class="line">          template = <span class="title function_">idToTemplate</span>(template);</span><br><span class="line">          <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">          <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; !template) &#123;</span><br><span class="line">            <span class="title function_">warn</span>(</span><br><span class="line">              <span class="string">`Template element not found or is empty: <span class="subst">$&#123;options.template&#125;</span>`</span>,</span><br><span class="line">              <span class="variable language_">this</span></span><br><span class="line">            );</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (template.<span class="property">nodeType</span>) &#123;</span><br><span class="line">        <span class="comment">//如果模板是元素，返回元素的innerHTML</span></span><br><span class="line">        template = template.<span class="property">innerHTML</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//如果不是字符串，也不是元素，在开发环境中会给出警告信息，模板不合法</span></span><br><span class="line">        <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">          <span class="title function_">warn</span>(<span class="string">&quot;invalid template option:&quot;</span> + template, <span class="variable language_">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//返回Vue实例。</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el) &#123;</span><br><span class="line">      <span class="comment">//如果选项中没有设置template模板，那么获取el的outerHTML 作为模板。</span></span><br><span class="line">      template = <span class="title function_">getOuterHTML</span>(el);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (template) &#123;</span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">        <span class="title function_">mark</span>(<span class="string">&quot;compile&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//把template模板编译成render函数</span></span><br><span class="line">      <span class="keyword">const</span> &#123; render, staticRenderFns &#125; = <span class="title function_">compileToFunctions</span>(</span><br><span class="line">        template,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">outputSourceRange</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>,</span><br><span class="line">          shouldDecodeNewlines,</span><br><span class="line">          shouldDecodeNewlinesForHref,</span><br><span class="line">          <span class="attr">delimiters</span>: options.<span class="property">delimiters</span>,</span><br><span class="line">          <span class="attr">comments</span>: options.<span class="property">comments</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="variable language_">this</span></span><br><span class="line">      );</span><br><span class="line">      options.<span class="property">render</span> = render;</span><br><span class="line">      options.<span class="property">staticRenderFns</span> = staticRenderFns;</span><br><span class="line"></span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">        <span class="title function_">mark</span>(<span class="string">&quot;compile end&quot;</span>);</span><br><span class="line">        <span class="title function_">measure</span>(<span class="string">`vue <span class="subst">$&#123;<span class="variable language_">this</span>._name&#125;</span> compile`</span>, <span class="string">&quot;compile&quot;</span>, <span class="string">&quot;compile end&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//如果创建 Vue实例的时候，传递了render函数，这时会直接调用mount方法。</span></span><br><span class="line">  <span class="comment">// mount方法的作用就是渲染DOM,这块内容在下一小节会讲解到。</span></span><br><span class="line">  <span class="keyword">return</span> mount.<span class="title function_">call</span>(<span class="variable language_">this</span>, el, hydrating);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>下面代码是<code>query</code>方法实现的代码。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Query an element selector if it&#x27;s not an element already.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">query</span>(<span class="params">el: string | Element</span>): <span class="title class_">Element</span> &#123;</span><br><span class="line">  <span class="comment">// 如果el等于字符串，表明是选择器。</span></span><br><span class="line">  <span class="comment">//否则是DOM对象，直接返回</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> el === <span class="string">&quot;string&quot;</span>) &#123;</span><br><span class="line">    <span class="comment">//获取对应的DOM元素</span></span><br><span class="line">    <span class="keyword">const</span> selected = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(el);</span><br><span class="line">    <span class="keyword">if</span> (!selected) &#123;</span><br><span class="line">      <span class="comment">//如果没有找到，判断是否为开发模式，如果是开发模式</span></span><br><span class="line">      <span class="comment">//在控制台打印“找不到元素”</span></span><br><span class="line">      process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp;</span><br><span class="line">        <span class="title function_">warn</span>(<span class="string">&quot;Cannot find element: &quot;</span> + el);</span><br><span class="line">      <span class="comment">//这时会创建一个`div`元素返回。</span></span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//返回找到的dom元素</span></span><br><span class="line">    <span class="keyword">return</span> selected;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> el;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>看完上面的代码后，我们就可以回答最开始的时候，提出的问题，如果传递了<code>render</code>函数，是不会处理<code>template</code>这个模板的，直接调用<code>mount</code>方法渲染<code>dom</code></p>
<p>现在面临的一个问题就是<code>$mount</code>这个方法是在哪儿被调用的呢？</p>
<p>在<code>core/instance/init.js</code>文件中，查找到如下代码：</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">if (vm.$options.el) &#123;</span><br><span class="line">     vm.$mount(vm.$options.el)</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>

<p>通过以上代码，可以看到调用了<code>$mount</code>方法。</p>
<p>也就是在<code>Vue._init</code>方法中调用的。</p>
<p>那么<code>_init</code>方法是在哪被调用的呢？<br>在<code>core/instance/index.js</code>文件中、</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Vue</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; !(<span class="variable language_">this</span> <span class="keyword">instanceof</span> <span class="title class_">Vue</span>)) &#123;</span><br><span class="line">    <span class="title function_">warn</span>(<span class="string">&quot;Vue is a constructor and should be called with the `new` keyword&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">_init</span>(options);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>通过以上的代码，可以看到在<code>Vue</code>这个方法中调用了<code>_init</code>方法，而<code>Vue</code>方法，是在创建<code>Vue</code>实例的时候被调用的。所以上面的<code>Vue</code>方法就是一个构造函数。</p>
<p>现在我们将以上的内容做一个总结，重点是以下三点内容。</p>
<ul>
<li><code>el</code>不能是<code>body</code> 或者是<code>html</code>标签</li>
<li>如果没有<code>render</code>，把<code>template</code>转换成<code>render</code>函数。</li>
<li>如果有<code>render</code>方法，直接调用<code>mount</code>挂载<code>DOM</code></li>
</ul>
<h1 id="7、Vue-的初始化过程"><a href="#7、Vue-的初始化过程" class="headerlink" title="7、Vue 的初始化过程"></a>7、Vue 的初始化过程</h1><p>在这一小节中，我们需要考虑如下的一个问题。</p>
<p><code>Vue</code>实例成员和<code>Vue</code>的静态成员是从哪里来的？</p>
<p>在<code>src/platforms/web</code>目录下面定义的文件都是与平台有关的文件。</p>
<p>下面我们还是看一下开始文件：<code>entry-runtime-with-compiler.js</code>文件。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* @flow */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> config <span class="keyword">from</span> <span class="string">&quot;core/config&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; warn, cached &#125; <span class="keyword">from</span> <span class="string">&quot;core/util/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; mark, measure &#125; <span class="keyword">from</span> <span class="string">&quot;core/util/perf&quot;</span>;</span><br><span class="line"><span class="comment">//导入Vue的构造函数</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Vue</span> <span class="keyword">from</span> <span class="string">&quot;./runtime/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; query &#125; <span class="keyword">from</span> <span class="string">&quot;./util/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; compileToFunctions &#125; <span class="keyword">from</span> <span class="string">&quot;./compiler/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  shouldDecodeNewlines,</span><br><span class="line">  shouldDecodeNewlinesForHref,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;./util/compat&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> idToTemplate = <span class="title function_">cached</span>(<span class="function">(<span class="params">id</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> el = <span class="title function_">query</span>(id);</span><br><span class="line">  <span class="keyword">return</span> el &amp;&amp; el.<span class="property">innerHTML</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//保留Vue实例的$mount方法，方便下面重写$mount的功能</span></span><br><span class="line"><span class="keyword">const</span> mount = <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span>;</span><br><span class="line"><span class="comment">//$mout:挂载，作用就是把生成的DOM挂载到页面中。</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span> = <span class="keyword">function</span> (<span class="params"></span></span><br><span class="line"><span class="params">  el?: string | Element,</span></span><br><span class="line"><span class="params">  <span class="comment">//非ssr情况下为false,ssr的时候为true</span></span></span><br><span class="line"><span class="params">  hydrating?: boolean</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Component</span> &#123;</span><br><span class="line">  <span class="comment">//获取el选项，创建vue实例的时候传递过来的选项。</span></span><br><span class="line">  <span class="comment">//el就是DOM对象。</span></span><br><span class="line">  el = el &amp;&amp; <span class="title function_">query</span>(el);</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">  <span class="comment">//如果el为body或者是html,并且是开发环境，那么会在浏览器的控制台</span></span><br><span class="line">  <span class="comment">//中输出不能将Vue的实例挂载到&lt;html&gt;或者是&lt;body&gt;标签上</span></span><br><span class="line">  <span class="keyword">if</span> (el === <span class="variable language_">document</span>.<span class="property">body</span> || el === <span class="variable language_">document</span>.<span class="property">documentElement</span>) &#123;</span><br><span class="line">    process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp;</span><br><span class="line">      <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">`Do not mount Vue to &lt;html&gt; or &lt;body&gt; - mount to normal elements instead.`</span></span><br><span class="line">      );</span><br><span class="line">    <span class="comment">//直接返回vue的实例</span></span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//获取options选项</span></span><br><span class="line">  <span class="keyword">const</span> options = <span class="variable language_">this</span>.<span class="property">$options</span>;</span><br><span class="line">  <span class="comment">// resolve template/el and convert to render function</span></span><br><span class="line">  <span class="comment">//判断options中是否有render(在创建vue实例的时候，也就new Vue的时候是否传递了render函数)</span></span><br><span class="line">  <span class="keyword">if</span> (!options.<span class="property">render</span>) &#123;</span><br><span class="line">    <span class="comment">//没有传递render函数。获取template模板，然后将其转换成render函数</span></span><br><span class="line">    <span class="comment">//关于将`template`转换成render的代码比较多，目录先知道其主要作用就可以了</span></span><br><span class="line">    <span class="keyword">let</span> template = options.<span class="property">template</span>;</span><br><span class="line">    <span class="comment">// 如果模板存在</span></span><br><span class="line">    <span class="keyword">if</span> (template) &#123;</span><br><span class="line">      <span class="comment">//判断对应的类型如果是字符串</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> template === <span class="string">&quot;string&quot;</span>) &#123;</span><br><span class="line">        <span class="comment">//如果模板是id选择器</span></span><br><span class="line">        <span class="keyword">if</span> (template.<span class="title function_">charAt</span>(<span class="number">0</span>) === <span class="string">&quot;#&quot;</span>) &#123;</span><br><span class="line">          <span class="comment">//获取对应的DOM对象的innerHTML</span></span><br><span class="line">          template = <span class="title function_">idToTemplate</span>(template);</span><br><span class="line">          <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">          <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; !template) &#123;</span><br><span class="line">            <span class="title function_">warn</span>(</span><br><span class="line">              <span class="string">`Template element not found or is empty: <span class="subst">$&#123;options.template&#125;</span>`</span>,</span><br><span class="line">              <span class="variable language_">this</span></span><br><span class="line">            );</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (template.<span class="property">nodeType</span>) &#123;</span><br><span class="line">        <span class="comment">//如果模板是元素，返回元素的innerHTML</span></span><br><span class="line">        template = template.<span class="property">innerHTML</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">          <span class="title function_">warn</span>(<span class="string">&quot;invalid template option:&quot;</span> + template, <span class="variable language_">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el) &#123;</span><br><span class="line">      <span class="comment">//如果模板不存在</span></span><br><span class="line">      template = <span class="title function_">getOuterHTML</span>(el);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (template) &#123;</span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">        <span class="title function_">mark</span>(<span class="string">&quot;compile&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//把template模板编译成render函数</span></span><br><span class="line">      <span class="keyword">const</span> &#123; render, staticRenderFns &#125; = <span class="title function_">compileToFunctions</span>(</span><br><span class="line">        template,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">outputSourceRange</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>,</span><br><span class="line">          shouldDecodeNewlines,</span><br><span class="line">          shouldDecodeNewlinesForHref,</span><br><span class="line">          <span class="attr">delimiters</span>: options.<span class="property">delimiters</span>,</span><br><span class="line">          <span class="attr">comments</span>: options.<span class="property">comments</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="variable language_">this</span></span><br><span class="line">      );</span><br><span class="line">      options.<span class="property">render</span> = render;</span><br><span class="line">      options.<span class="property">staticRenderFns</span> = staticRenderFns;</span><br><span class="line"></span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">        <span class="title function_">mark</span>(<span class="string">&quot;compile end&quot;</span>);</span><br><span class="line">        <span class="title function_">measure</span>(<span class="string">`vue <span class="subst">$&#123;<span class="variable language_">this</span>._name&#125;</span> compile`</span>, <span class="string">&quot;compile&quot;</span>, <span class="string">&quot;compile end&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//如果创建 Vue实例的时候，传递了render函数，这时会直接调用mount方法。</span></span><br><span class="line">  <span class="comment">// mount方法的作用就是渲染DOM，这里的mount就是下面我们要看的./runtime/index文件中的$mount,只不过</span></span><br><span class="line">  <span class="comment">// 在当前的文件中重写了。</span></span><br><span class="line">  <span class="keyword">return</span> mount.<span class="title function_">call</span>(<span class="variable language_">this</span>, el, hydrating);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Get outerHTML of elements, taking care</span></span><br><span class="line"><span class="comment"> * of SVG elements in IE as well.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getOuterHTML</span>(<span class="params">el: Element</span>): string &#123;</span><br><span class="line">  <span class="keyword">if</span> (el.<span class="property">outerHTML</span>) &#123;</span><br><span class="line">    <span class="comment">//如果有outerHTML属性，返回内容的HTML形式</span></span><br><span class="line">    <span class="keyword">return</span> el.<span class="property">outerHTML</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">//创建div</span></span><br><span class="line">    <span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">    <span class="comment">//把el的内容克隆，然后追加到div中</span></span><br><span class="line">    container.<span class="title function_">appendChild</span>(el.<span class="title function_">cloneNode</span>(<span class="literal">true</span>));</span><br><span class="line">    <span class="comment">//返回div的innerHTML</span></span><br><span class="line">    <span class="keyword">return</span> container.<span class="property">innerHTML</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//注册Vue.compile方法，根据HTML字符串返回render函数</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">compile</span> = compileToFunctions;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Vue</span>;</span><br></pre></td></tr></table></figure>

<p>通过查看该文件中的代码，可以发现，在该文件中并没有创建<code>Vue</code>的实例，关于实例的创建在如下导入的文件中。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//导入Vue的构造函数</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Vue</span> <span class="keyword">from</span> <span class="string">&quot;./runtime/index&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>通过前面的讲解，我们知道在入口文件中，最主要的方法是<code>mount</code>.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//保留Vue实例的$mount方法，方便下面重写$mount的功能</span></span><br><span class="line"><span class="keyword">const</span> mount = <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span>;</span><br></pre></td></tr></table></figure>

<p>在该方法中，很重要的一个操作就是将<code>template</code>模板，转换成<code>render</code>函数。</p>
<p>下面，我们先来看一下<code>./runtime/index</code>文件中的内容。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// install platform specific utils</span></span><br><span class="line"><span class="comment">//给Vue.config注册了方法，这些方法都是与平台相关的方法。这些方法是在Vue内部使用的。</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">mustUseProp</span> = mustUseProp;</span><br><span class="line"><span class="comment">//是否为保留的标签，也就是说，传递过来的内容是否为HTML中特有的标签</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">isReservedTag</span> = isReservedTag;</span><br><span class="line"><span class="comment">//是否是保留的属性，也就是说，传递过来的内容是否为HTML中特有的属性</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">isReservedAttr</span> = isReservedAttr;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">getTagNamespace</span> = getTagNamespace;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">isUnknownElement</span> = isUnknownElement;</span><br></pre></td></tr></table></figure>

<p>以上内容简短了解一下就可以。</p>
<p>下面我们继续查看如下内容：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// install platform runtime directives &amp; components</span></span><br><span class="line"><span class="comment">//通过extend方法注册了与平台相关的全局的指令与组件。</span></span><br><span class="line"><span class="comment">//extend的作用就是将第二个参数的成员全部拷贝到第一个参数中</span></span><br><span class="line"><span class="comment">//那么问题是注册了哪些指令与组件呢？</span></span><br><span class="line"><span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">directives</span>, platformDirectives);</span><br><span class="line"><span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">components</span>, platformComponents);</span><br></pre></td></tr></table></figure>

<p>那么问题是注册了哪些指令与组件呢？</p>
<p>我们可以查看，<code>extend</code>的第二个参数，以上<code>extend</code>参数的内容，分别来自如下两个文件。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> platformDirectives <span class="keyword">from</span> <span class="string">&quot;./directives/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> platformComponents <span class="keyword">from</span> <span class="string">&quot;./components/index&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>我们首先看一下<code>./directives/index</code>文件中的内容，如下所示：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> model <span class="keyword">from</span> <span class="string">&quot;./model&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> show <span class="keyword">from</span> <span class="string">&quot;./show&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  model,</span><br><span class="line">  show,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>通过以上代码，我们可以看到这个文件中导出了<code>v-show</code>与<code>v-model</code>这两个指令。</p>
<p>下面再看一下<code>./components/index</code>文件中的内容。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Transition</span> <span class="keyword">from</span> <span class="string">&quot;./transition&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">TransitionGroup</span> <span class="keyword">from</span> <span class="string">&quot;./transition-group&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="title class_">Transition</span>,</span><br><span class="line">  <span class="title class_">TransitionGroup</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>以上文件导出的就是<code>v-Transition</code>与<code>v-TransitionGroup</code>这两个组件的内容。</p>
<p>通过下面两行代码，我们还可以发现一个相应的问题。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">directives</span>, platformDirectives);</span><br><span class="line"><span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">components</span>, platformComponents);</span><br></pre></td></tr></table></figure>

<p>就是全局的指令与组件分别存储到了<code>Vue.options.directives</code>与<code>Vue.options.components</code>中。</p>
<p>例如，我们在项目中使用<code>Vue.component</code>注册的组件，都存储到了<code>Vue.options.components</code>中，也就说存储了<code>Vue.options.components</code>中的组件都是全局可以访问的。</p>
<p>我们继续向下看，如下代码</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// install platform patch function</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">__patch__</span> = inBrowser ? patch : noop;</span><br></pre></td></tr></table></figure>

<p>以上代码就是在<code>Vue</code>的原型中注册了<code>__patch__</code>函数，该函数对我们来说比较熟悉了，在学习虚拟<code>DOM</code>的时候，我们知道<code>patch</code>函数的作用就是将虚拟<code>DOM</code>转换成真实的<code>DOM</code>.在给<code>patch</code>函数赋值的时候，首先判断是否为浏览器的环境，如果是则返回<code>patch</code>,否则返回<code>noop</code>,<code>noop</code>是一个空函数。</p>
<p>这里有一个问题就是：<code>inBrowser</code>是怎样判断是否为浏览器环境的呢？</p>
<p><code>inBrowser</code>定义在如下文件中<code>import &#123; devtools, inBrowser &#125; from &quot;core/util/index&quot;;</code></p>
<p>该文件的代码如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* @flow */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;shared/util&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./lang&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./env&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./options&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./debug&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./props&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./error&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./next-tick&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> &#123; defineReactive &#125; <span class="keyword">from</span> <span class="string">&quot;../observer/index&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>通过以上的代码，我们并没有发现<code>isBrowser</code>的定义，那么很明显都被封装到具体的文件中了。</p>
<p><code>isBrowser</code>是与环境有关的内容，所以很明显来自<code>env</code>这个文件，在该文件中，可以看到如下代码：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> inBrowser = <span class="keyword">typeof</span> <span class="variable language_">window</span> !== <span class="string">&quot;undefined&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>如果<code>window</code>对象不等于<code>undefined</code>,表明当前的环境就是浏览器的环境</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//给Vue原型注册了$mount方法，也就是给Vue的实例注册了$mount方法，在entry-runtime-with-compiler.js文件中对该方法进行了重写。</span></span><br><span class="line"><span class="comment">//在该方法中调用了mountComponent方法，用来渲染DOM</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span> = <span class="keyword">function</span> (<span class="params"></span></span><br><span class="line"><span class="params">  el?: string | Element,</span></span><br><span class="line"><span class="params">  hydrating?: boolean</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Component</span> &#123;</span><br><span class="line">  <span class="comment">//这里重新获取el,并且判断是否为浏览器环境.</span></span><br><span class="line">  <span class="comment">//这里有一个问题，就是为什么要重新获取el,在entry-runtime-with-compiler.js文件中重写$mount方法的时候也获取了el,这里为什么要重新获取呢？</span></span><br><span class="line">  <span class="comment">//原因是：entry-runtime-with-compiler.js是带编译器版本的Vue,而当前文件是运行时版本执行的，那么就不会执行entry-runtime-with-compiler.js文件来获取el,所以这里必须要重新获取el。</span></span><br><span class="line">  el = el &amp;&amp; inBrowser ? <span class="title function_">query</span>(el) : <span class="literal">undefined</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">mountComponent</span>(<span class="variable language_">this</span>, el, hydrating);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>下面看一下<code>mountComponent</code>方法的内部实现。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">import &#123; mountComponent &#125; from &quot;core/instance/lifecycle&quot;;</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">mountComponent</span>(<span class="params"></span></span><br><span class="line"><span class="params">  vm: Component,</span></span><br><span class="line"><span class="params">  el: ?Element,</span></span><br><span class="line"><span class="params">  hydrating?: boolean</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Component</span> &#123;</span><br><span class="line">  vm.<span class="property">$el</span> = el; <span class="comment">//把el赋值给Vue实例中的$el属性</span></span><br><span class="line">  <span class="comment">//判断$options中是否有render函数。</span></span><br><span class="line">  <span class="keyword">if</span> (!vm.<span class="property">$options</span>.<span class="property">render</span>) &#123;</span><br><span class="line">    vm.<span class="property">$options</span>.<span class="property">render</span> = createEmptyVNode;</span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">      <span class="comment">//如果在运行时环境中，如果使用了template模板，会出现如下的警告信息。</span></span><br><span class="line">      <span class="comment">//警告信息：使用的是运行时版本，编译器无效，应该使用完整版，或者是编写render函数。</span></span><br><span class="line">      <span class="keyword">if</span> (</span><br><span class="line">        (vm.<span class="property">$options</span>.<span class="property">template</span> &amp;&amp; vm.<span class="property">$options</span>.<span class="property">template</span>.<span class="title function_">charAt</span>(<span class="number">0</span>) !== <span class="string">&quot;#&quot;</span>) ||</span><br><span class="line">        vm.<span class="property">$options</span>.<span class="property">el</span> ||</span><br><span class="line">        el</span><br><span class="line">      ) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">&quot;You are using the runtime-only build of Vue where the template &quot;</span> +</span><br><span class="line">            <span class="string">&quot;compiler is not available. Either pre-compile the templates into &quot;</span> +</span><br><span class="line">            <span class="string">&quot;render functions, or use the compiler-included build.&quot;</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">&quot;Failed to mount component: template or render function not defined.&quot;</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//触发beforeMount钩子函数，表示的是挂载之前。</span></span><br><span class="line">  <span class="title function_">callHook</span>(vm, <span class="string">&quot;beforeMount&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> updateComponent; <span class="comment">//完成组件的更新</span></span><br><span class="line">  <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">    updateComponent = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> name = vm.<span class="property">_name</span>;</span><br><span class="line">      <span class="keyword">const</span> id = vm.<span class="property">_uid</span>;</span><br><span class="line">      <span class="keyword">const</span> startTag = <span class="string">`vue-perf-start:<span class="subst">$&#123;id&#125;</span>`</span>;</span><br><span class="line">      <span class="keyword">const</span> endTag = <span class="string">`vue-perf-end:<span class="subst">$&#123;id&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">      <span class="title function_">mark</span>(startTag);</span><br><span class="line">      <span class="keyword">const</span> vnode = vm.<span class="title function_">_render</span>();</span><br><span class="line">      <span class="title function_">mark</span>(endTag);</span><br><span class="line">      <span class="title function_">measure</span>(<span class="string">`vue <span class="subst">$&#123;name&#125;</span> render`</span>, startTag, endTag);</span><br><span class="line"></span><br><span class="line">      <span class="title function_">mark</span>(startTag);</span><br><span class="line">      vm.<span class="title function_">_update</span>(vnode, hydrating);</span><br><span class="line">      <span class="title function_">mark</span>(endTag);</span><br><span class="line">      <span class="title function_">measure</span>(<span class="string">`vue <span class="subst">$&#123;name&#125;</span> patch`</span>, startTag, endTag);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">//重点看这一段代码：</span></span><br><span class="line">    <span class="comment">// vm._render():表示执行的是用户传入的render函数，或者是执行编译器生成的render函数。</span></span><br><span class="line">    <span class="comment">// render( )函数最终会返回虚拟DOM,把返回的虚拟DOM传递给_update函数。</span></span><br><span class="line">    <span class="comment">// _update函数，会将虚拟DOM转换成真实的DOM。</span></span><br><span class="line">    <span class="comment">//也就说该方法执行完毕后，页面会呈现出具体的内容。</span></span><br><span class="line">    updateComponent = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      vm.<span class="title function_">_update</span>(vm.<span class="title function_">_render</span>(), hydrating);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// we set this to vm._watcher inside the watcher&#x27;s constructor</span></span><br><span class="line">  <span class="comment">// since the watcher&#x27;s initial patch may call $forceUpdate (e.g. inside child</span></span><br><span class="line">  <span class="comment">// component&#x27;s mounted hook), which relies on vm._watcher being already defined</span></span><br><span class="line">  <span class="comment">//在 Watcher中调用了updateComponent方法。</span></span><br><span class="line">  <span class="comment">//</span></span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Watcher</span>(</span><br><span class="line">    vm,</span><br><span class="line">    updateComponent,</span><br><span class="line">    noop,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="title function_">before</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (vm.<span class="property">_isMounted</span> &amp;&amp; !vm.<span class="property">_isDestroyed</span>) &#123;</span><br><span class="line">          <span class="title function_">callHook</span>(vm, <span class="string">&quot;beforeUpdate&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="literal">true</span> <span class="comment">/* isRenderWatcher */</span></span><br><span class="line">  );</span><br><span class="line">  hydrating = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// manually mounted instance, call mounted on self</span></span><br><span class="line">  <span class="comment">// mounted is called for render-created child components in its inserted hook</span></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$vnode</span> == <span class="literal">null</span>) &#123;</span><br><span class="line">    vm.<span class="property">_isMounted</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">//触发了mounted钩子函数，表明页面已经挂载完毕了。</span></span><br><span class="line">    <span class="title function_">callHook</span>(vm, <span class="string">&quot;mounted&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> vm;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>mountComponent</code>后面的代码就是一些调试的代码，这里也不要在看。</p>
<p>下面，把我们看到的代码总结一下。以上代码定义的是与平台有关的代码，主要是注册了<code>patch</code>方法，以及<code>$mount</code>方法。还有就是注册了全局的指令与组件。</p>
<p>但在当前的这个文件中，我们还是没有看到<code>Vue</code>的构造函数。</p>
<p>在该文件的顶部，可以看到如下导入的语句。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">import Vue from &quot;core/index&quot;;</span><br></pre></td></tr></table></figure>

<p>该文件中的代码如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Vue</span> <span class="keyword">from</span> <span class="string">&quot;./instance/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; initGlobalAPI &#125; <span class="keyword">from</span> <span class="string">&quot;./global-api/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; isServerRendering &#125; <span class="keyword">from</span> <span class="string">&quot;core/util/env&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">FunctionalRenderContext</span> &#125; <span class="keyword">from</span> <span class="string">&quot;core/vdom/create-functional-component&quot;</span>;</span><br><span class="line"><span class="comment">//给Vue构造函数注册一些静态的方法。</span></span><br><span class="line"><span class="title function_">initGlobalAPI</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//以下通过defineProperty定义的内容都是与服务端渲染有关的内容。</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>, <span class="string">&quot;$isServer&quot;</span>, &#123;</span><br><span class="line">  <span class="attr">get</span>: isServerRendering,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>, <span class="string">&quot;$ssrContext&quot;</span>, &#123;</span><br><span class="line">  <span class="title function_">get</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">/* istanbul ignore next */</span></span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">$vnode</span> &amp;&amp; <span class="variable language_">this</span>.<span class="property">$vnode</span>.<span class="property">ssrContext</span>;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// expose FunctionalRenderContext for ssr runtime helper installation</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>, <span class="string">&quot;FunctionalRenderContext&quot;</span>, &#123;</span><br><span class="line">  <span class="attr">value</span>: <span class="title class_">FunctionalRenderContext</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//指定Vue版本。</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">version</span> = <span class="string">&quot;__VERSION__&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Vue</span>;</span><br></pre></td></tr></table></figure>

<p>下面我们来看一下<code>initGlobalAPI</code>方法中的代码。</p>
<p>该方法的代码定义在<code>core/global-api/index.js</code>文件中。</p>
<p>具体的代码如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* @flow */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> config <span class="keyword">from</span> <span class="string">&quot;../config&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; initUse &#125; <span class="keyword">from</span> <span class="string">&quot;./use&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; initMixin &#125; <span class="keyword">from</span> <span class="string">&quot;./mixin&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; initExtend &#125; <span class="keyword">from</span> <span class="string">&quot;./extend&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; initAssetRegisters &#125; <span class="keyword">from</span> <span class="string">&quot;./assets&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; set, del &#125; <span class="keyword">from</span> <span class="string">&quot;../observer/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">ASSET_TYPES</span> &#125; <span class="keyword">from</span> <span class="string">&quot;shared/constants&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> builtInComponents <span class="keyword">from</span> <span class="string">&quot;../components/index&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; observe &#125; <span class="keyword">from</span> <span class="string">&quot;core/observer/index&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  warn,</span><br><span class="line">  extend,</span><br><span class="line">  nextTick,</span><br><span class="line">  mergeOptions,</span><br><span class="line">  defineReactive,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;../util/index&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initGlobalAPI</span>(<span class="params">Vue: GlobalAPI</span>) &#123;</span><br><span class="line">  <span class="comment">// config</span></span><br><span class="line">  <span class="keyword">const</span> configDef = &#123;&#125;;</span><br><span class="line">  configDef.<span class="property">get</span> = <span class="function">() =&gt;</span> config;</span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">    configDef.<span class="property">set</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">&quot;Do not replace the Vue.config object, set individual fields instead.&quot;</span></span><br><span class="line">      );</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 初始化了`Vue.config`对象，该对象是Vue的静态成员</span></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>, <span class="string">&quot;config&quot;</span>, configDef);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// exposed util methods.</span></span><br><span class="line">  <span class="comment">// <span class="doctag">NOTE:</span> these are not considered part of the public API - avoid relying on</span></span><br><span class="line">  <span class="comment">// them unless you are aware of the risk.</span></span><br><span class="line">  <span class="comment">//这些工具方法不视作全局API的一部分，除非你已经意识到某些风险，否则不要去依赖他们，也就是说，使用这些API会出现一些问题。</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">util</span> = &#123;</span><br><span class="line">    warn,</span><br><span class="line">    extend,</span><br><span class="line">    mergeOptions,</span><br><span class="line">    defineReactive,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">//静态方法set/delete/nextTick</span></span><br><span class="line">  <span class="comment">//挂载到了Vue的构造函数中。后期会继续看内部源码的实现</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">set</span> = set;</span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">delete</span> = del;</span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">nextTick</span> = nextTick;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2.6 explicit observable API</span></span><br><span class="line">  <span class="comment">//让一个对象变成可响应式的，内部调用了observe方法。</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">observable</span> = &lt;T&gt;(<span class="attr">obj</span>: T): <span class="function"><span class="params">T</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">observe</span>(obj);</span><br><span class="line">    <span class="keyword">return</span> obj;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//初始化了Vue.options对象，并给其扩展了</span></span><br><span class="line">  <span class="comment">//components/directives/filters内容，后面还会看这块内容</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">options</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="variable constant_">ASSET_TYPES</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">type</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title class_">Vue</span>.<span class="property">options</span>[type + <span class="string">&quot;s&quot;</span>] = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// this is used to identify the &quot;base&quot; constructor to extend all plain-object</span></span><br><span class="line">  <span class="comment">// components with in Weex&#x27;s multi-instance scenarios.</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">_base</span> = <span class="title class_">Vue</span>;</span><br><span class="line">  <span class="comment">//设置keep-alive组件</span></span><br><span class="line">  <span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">components</span>, builtInComponents);</span><br><span class="line">  <span class="comment">//注册Vue.use(),用来注册插件</span></span><br><span class="line">  <span class="title function_">initUse</span>(<span class="title class_">Vue</span>);</span><br><span class="line">  <span class="comment">//注册Vue.mixin( )实现混入</span></span><br><span class="line">  <span class="title function_">initMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line">  <span class="comment">//注册Vue.extend( )基于传入的options返回一个组件的构造函数</span></span><br><span class="line">  <span class="title function_">initExtend</span>(<span class="title class_">Vue</span>);</span><br><span class="line">  <span class="comment">// 注册Vue.directive(),Vue.component( ),Vue.filter( )</span></span><br><span class="line">  <span class="title function_">initAssetRegisters</span>(<span class="title class_">Vue</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>目前我们只需要知道在<code>initGlobalAPI</code>方法，初始化了<code>Vue</code>的静态方法。</p>
<p>现在，我们回到<code>import Vue from &quot;core/index&quot;;</code> 文件，发现在该文件中也没有创建<code>Vue</code>的构造函数，</p>
<p>但是在其顶部，导入了：<code>import Vue from &quot;./instance/index&quot;;</code></p>
<p>打开该文件查看的代码如下所示：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; initMixin &#125; <span class="keyword">from</span> <span class="string">&quot;./init&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; stateMixin &#125; <span class="keyword">from</span> <span class="string">&quot;./state&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; renderMixin &#125; <span class="keyword">from</span> <span class="string">&quot;./render&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; eventsMixin &#125; <span class="keyword">from</span> <span class="string">&quot;./events&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; lifecycleMixin &#125; <span class="keyword">from</span> <span class="string">&quot;./lifecycle&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; warn &#125; <span class="keyword">from</span> <span class="string">&quot;../util/index&quot;</span>;</span><br><span class="line"><span class="comment">//Vue构造函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Vue</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="comment">//判断是否为生产环境，如果不等于生产环境并且如果this不是Vue的实例</span></span><br><span class="line">  <span class="comment">//那么说明用户将其作为普通函数调用，而不是通过new来创建其实例，所以会出现如下错误提示</span></span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; !(<span class="variable language_">this</span> <span class="keyword">instanceof</span> <span class="title class_">Vue</span>)) &#123;</span><br><span class="line">    <span class="title function_">warn</span>(<span class="string">&quot;Vue is a constructor and should be called with the `new` keyword&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//调用_init( )方法</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">_init</span>(options);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//注册vm的_init( )方法，初始化vm</span></span><br><span class="line"><span class="title function_">initMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//注册vm（Vue实例）的$data/$props/$set/$delete/$watch</span></span><br><span class="line"><span class="title function_">stateMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//初始化事件相关的方法</span></span><br><span class="line"><span class="comment">//$on/$once/$off/$emit</span></span><br><span class="line"><span class="title function_">eventsMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//初始化生命周期相关的混入方法</span></span><br><span class="line"><span class="comment">// $forceUpdate/$destroy</span></span><br><span class="line"><span class="title function_">lifecycleMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//混入render</span></span><br><span class="line"><span class="comment">// $nextTick</span></span><br><span class="line"><span class="title function_">renderMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Vue</span>;</span><br></pre></td></tr></table></figure>

<p>下面看一下<code>stateMixin</code>方法，在该方法中，我们可以看到如下的代码</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//在Vue的实例总初始化了一些属性和者方法。</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>, <span class="string">&quot;$data&quot;</span>, dataDef);</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>, <span class="string">&quot;$props&quot;</span>, propsDef);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$set</span> = set;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$delete</span> = del;</span><br></pre></td></tr></table></figure>

<p>通过上面的代码，我们可以看到在<code>stateMixin</code>方法中为<code>Vue</code>的原型上注册了对应的属性和方法，也就说在这个位置实现了为<code>Vue</code>的实例初始化属性和方法。</p>
<p>到此位置，我们最开始提出的问题：</p>
<p><code>Vue</code>实例成员和<code>Vue</code>的静态成员是从哪里来的？</p>
<p>现在已经全部找到。</p>
<p>关于<code>import Vue from &quot;./instance/index&quot;;</code>文件中的其他方法，后续课程内容中还会查看。</p>
<p>目前我们只需要知道，在在该文件中，创建了<code>Vue</code>的构造函数，并且设置了<code>Vue</code>实例的成员。</p>
<p>这里还有一个小的问题就是，在创建<code>Vue</code>的实例的时候，这里使用了构造函数，而没有使用类(<code>class</code>)的形式，</p>
<p>原因是：因为使用类来实现构造函数的，下面的方法就不容易实现。在这些方法中为<code>Vue</code>的原型上挂在了很多的成员，而使用类的构造函数不容易实现。</p>
<p>现在我们将这块内容做一总结：</p>
<p>通过前面的讲解，我们看了四个文件</p>
<p><code>src/platforms/web/entry-runtime-with-compiler.js</code></p>
<ul>
<li><code>web</code>平台相关的入口</li>
<li>重写了平台相关的<code>$mount( )</code>方法，把<code>template</code>模板转换成<code>render</code>函数</li>
<li>注册了<code>Vue.compile( )</code>方法，可以根据传递的<code>HTML</code>字符串返回<code>render</code>函数</li>
</ul>
<p><code>src/platforms/web/runtime/index.js</code></p>
<ul>
<li><p><code>web</code>平台相关</p>
</li>
<li><p>注册和平台相关的全局指令：<code>v-model</code>,<code>v-show</code></p>
</li>
<li><p>注册和平台相关的全局组件:<code>v-transition</code>,<code>v-transition-group</code></p>
</li>
<li><p>全局的指令与组件分别存储到了<code>Vue.options.directives</code>与<code>Vue.options.components</code>中。</p>
</li>
<li><p>全局方法</p>
<p>​ <code>__patch__</code>：把虚拟<code>DOM</code>转换成真实<code>DOM</code></p>
<p>​ <code>$mount</code>：挂载方法，把<code>DOM</code>渲染到页面中，在<code>src/platforms/web/entry-runtime-with-compiler.js</code>文件中重写了</p>
<p><code>$mount</code>,使其具有了编译的能力。</p>
</li>
<li><p><code>src/core/index.js</code></p>
<p>与平台无关</p>
<p>设置了<code>Vue</code>的静态方法，<code>initGlobalAPI(Vue)</code></p>
</li>
<li><p><code>src/core/instance/index.js</code></p>
<p>与平台无关</p>
<p>定义了<code>Vue</code>的构造方法，调用了<code>this._init(options)</code>方法（该方法是整个程序的入口）</p>
<p>给<code>Vue</code>中混入了常用的实例成员。</p>
</li>
</ul>
<h1 id="8、静态成员初始化"><a href="#8、静态成员初始化" class="headerlink" title="8、静态成员初始化"></a>8、静态成员初始化</h1><p>​ 这一小节我们来看一下<code>Vue</code>中静态成员的初始化，通过前面的讲解，我们知道静态成员都是在文件<code>src/core/index.js</code>中完成初始化，下面再看一下该文件。</p>
<p>在该文件有一个<code>initGlobalAPI</code>方法，在该方法中注册了一些静态成员。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">//给Vue构造函数注册一些静态的方法，属性。</span><br><span class="line">initGlobalAPI(Vue);</span><br></pre></td></tr></table></figure>

<p><code>initGlobalAPI</code>方法的具体实现如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initGlobalAPI</span>(<span class="params">Vue: GlobalAPI</span>) &#123;</span><br><span class="line">  <span class="comment">// config</span></span><br><span class="line">  <span class="keyword">const</span> configDef = &#123;&#125;;</span><br><span class="line">  <span class="comment">//为configDef对象添加一个get方法，返回一个config对象</span></span><br><span class="line">  configDef.<span class="property">get</span> = <span class="function">() =&gt;</span> config;</span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">    <span class="comment">//如果不是生产环境，则为开发环境，这时会为configDef添加一个set方法，如果为config进行赋值操作，会出现不能给`Vue.config`重新赋值的错误。</span></span><br><span class="line">    configDef.<span class="property">set</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">&quot;Do not replace the Vue.config object, set individual fields instead.&quot;</span></span><br><span class="line">      );</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 初始化了`Vue.config`对象，该对象是Vue的静态成员</span></span><br><span class="line">  <span class="comment">//这里不是定义响应式数据，而是为Vue定义了一个config属性</span></span><br><span class="line">  <span class="comment">//并且为其设置了configDef约束。</span></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>, <span class="string">&quot;config&quot;</span>, configDef);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// exposed util methods.</span></span><br><span class="line">  <span class="comment">// <span class="doctag">NOTE:</span> these are not considered part of the public API - avoid relying on</span></span><br><span class="line">  <span class="comment">// them unless you are aware of the risk.</span></span><br><span class="line">  <span class="comment">//这些工具方法不视作全局API的一部分，除非你已经意识到某些风险，否则不要去依赖他们，也就是说，使用这些API会出现一些问题。</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">util</span> = &#123;</span><br><span class="line">    warn,</span><br><span class="line">    extend,</span><br><span class="line">    mergeOptions,</span><br><span class="line">    defineReactive,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">//静态方法set/delete/nextTick</span></span><br><span class="line">  <span class="comment">//挂载到了Vue的构造函数中。后期会继续看内部源码的实现</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">set</span> = set;</span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">delete</span> = del;</span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">nextTick</span> = nextTick;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2.6 explicit observable API</span></span><br><span class="line">  <span class="comment">//让一个对象变成可响应式的，内部调用了observe方法。</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">observable</span> = &lt;T&gt;(<span class="attr">obj</span>: T): <span class="function"><span class="params">T</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">observe</span>(obj);</span><br><span class="line">    <span class="keyword">return</span> obj;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">//初始化了Vue.options对象，并给其扩展了</span></span><br><span class="line">  <span class="comment">//components/directives/filters内容</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">options</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="variable constant_">ASSET_TYPES</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">type</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title class_">Vue</span>.<span class="property">options</span>[type + <span class="string">&quot;s&quot;</span>] = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// this is used to identify the &quot;base&quot; constructor to extend all plain-object</span></span><br><span class="line">  <span class="comment">// components with in Weex&#x27;s multi-instance scenarios.</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">_base</span> = <span class="title class_">Vue</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">components</span>, builtInComponents);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">initUse</span>(<span class="title class_">Vue</span>);</span><br><span class="line">  <span class="title function_">initMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line">  <span class="title function_">initExtend</span>(<span class="title class_">Vue</span>);</span><br><span class="line">  <span class="title function_">initAssetRegisters</span>(<span class="title class_">Vue</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的代码中，首先初始化了<code>Vue.config</code>对象,并且添加了相应的约束。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 初始化了`Vue.config`对象，该对象是Vue的静态成员</span></span><br><span class="line"><span class="comment">//这里不是定义响应式数据，而是为Vue定义了一个config属性</span></span><br><span class="line"><span class="comment">//并且为其设置了configDef约束。</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>, <span class="string">&quot;config&quot;</span>, configDef);</span><br></pre></td></tr></table></figure>

<p>初始化<code>Vue.config</code>对象后，在什么位置为其挂载了成员。</p>
<p>在<code>platforms/web/runtime/index.js</code>文件中</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// install platform specific utils</span></span><br><span class="line"><span class="comment">//给Vue.config注册了方法，这些方法都是与平台相关的方法。这些方法是在Vue内部使用的。</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">mustUseProp</span> = mustUseProp;</span><br><span class="line"><span class="comment">//是否为保留的标签，也就是说，传递过来的内容是否为HTML中特有的标签</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">isReservedTag</span> = isReservedTag;</span><br><span class="line"><span class="comment">//是否是保留的属性，也就是说，传递过来的内容是否为HTML中特有的属性</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">isReservedAttr</span> = isReservedAttr;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">getTagNamespace</span> = getTagNamespace;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">isUnknownElement</span> = isUnknownElement;</span><br></pre></td></tr></table></figure>

<p>下面，我们来看如下的代码，前面的代码我们后面在讲解(<code>set</code>,<code>delete</code>,<code>observable</code>等内容)。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//初始化了Vue.options对象，并给其扩展了</span></span><br><span class="line"><span class="comment">//components/directives/filters内容</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//创建了Vue.options对象，并且没有指定原型。这样性能更高。</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">options</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="variable constant_">ASSET_TYPES</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">type</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">//从`ASSET_TYPES`数组中取出每一项，并为其添加了`s`,来作为Vue.options对象的属性。</span></span><br><span class="line">  <span class="comment">//也就是说给Vue.options中挂载了三个成员，分别是：components/directives/filters，并且都初始化成了空对象。这三个成员的作用是用来存储全局的组件，指令和过滤器，我们通过Vue.component,Vue.directive,Vue.filter创建的组件，指令，过滤器最终都会存储到Vue.options中的这三个成员中。</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">options</span>[type + <span class="string">&quot;s&quot;</span>] = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>对<code>ASSET_TYPES</code>数组进行遍历，那么该数组中存储的是什么内容呢？该数组具体定义的位置：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">ASSET_TYPES</span> &#125; <span class="keyword">from</span> <span class="string">&quot;shared/constants&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>在<code>src/shared</code>目录下面找到<code>constants.js</code>文件，该文件中的代码如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="variable constant_">SSR_ATTR</span> = <span class="string">&quot;data-server-rendered&quot;</span>;</span><br><span class="line"><span class="comment">//该数组中定义了我们比较常见的Vue.component,Vue.directive,Vue.filter的方法名称。</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="variable constant_">ASSET_TYPES</span> = [<span class="string">&quot;component&quot;</span>, <span class="string">&quot;directive&quot;</span>, <span class="string">&quot;filter&quot;</span>];</span><br><span class="line"><span class="comment">//生命周期的钩子函数名称，后面在讲解这块内容</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="variable constant_">LIFECYCLE_HOOKS</span> = [</span><br><span class="line">  <span class="string">&quot;beforeCreate&quot;</span>,</span><br><span class="line">  <span class="string">&quot;created&quot;</span>,</span><br><span class="line">  <span class="string">&quot;beforeMount&quot;</span>,</span><br><span class="line">  <span class="string">&quot;mounted&quot;</span>,</span><br><span class="line">  <span class="string">&quot;beforeUpdate&quot;</span>,</span><br><span class="line">  <span class="string">&quot;updated&quot;</span>,</span><br><span class="line">  <span class="string">&quot;beforeDestroy&quot;</span>,</span><br><span class="line">  <span class="string">&quot;destroyed&quot;</span>,</span><br><span class="line">  <span class="string">&quot;activated&quot;</span>,</span><br><span class="line">  <span class="string">&quot;deactivated&quot;</span>,</span><br><span class="line">  <span class="string">&quot;errorCaptured&quot;</span>,</span><br><span class="line">  <span class="string">&quot;serverPrefetch&quot;</span>,</span><br><span class="line">];</span><br></pre></td></tr></table></figure>

<p>现在，我们知道了<code>ASSET_TYPES</code>数组中的内容，然后在来看一下对应的循环内容。</p>
<p>看完循环内容后，我们再来看一下如下代码：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//将Vue的构造函数存储到了_base属性中，后期会用到。</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">_base</span> = <span class="title class_">Vue</span>;</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//设置keep-alive组件</span></span><br><span class="line"><span class="comment">//extend方法的作用是将第二个参数中的属性，拷贝到第一个参数中。下面可以看一下extend方法的具体实现。</span></span><br><span class="line"><span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">components</span>, builtInComponents);</span><br></pre></td></tr></table></figure>

<p><code>extend</code>方法定义在</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  warn,</span><br><span class="line">  extend,</span><br><span class="line">  nextTick,</span><br><span class="line">  mergeOptions,</span><br><span class="line">  defineReactive,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;../util/index&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>查看<code>until/index</code>中的内容</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;shared/util&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./lang&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./env&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./options&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./debug&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./props&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./error&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&quot;./next-tick&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> &#123; defineReactive &#125; <span class="keyword">from</span> <span class="string">&quot;../observer/index&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>在<code>src/shared/util</code>文件中，打开该文件搜索<code>extend</code></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">extend</span>(<span class="params">to: <span class="built_in">Object</span>, _from: ?<span class="built_in">Object</span></span>): <span class="title class_">Object</span> &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> _from) &#123;</span><br><span class="line">    to[key] = _from[key];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> to;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实现了一个浅拷贝。把一个对象的成员拷贝给另外一个对象</p>
<p>下面的代码是将<code>builtInComponents</code>拷贝给<code>Vue.options.components</code>,这里完成的就是全局组件的注册</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//设置keep-alive组件</span></span><br><span class="line"><span class="title function_">extend</span>(<span class="title class_">Vue</span>.<span class="property">options</span>.<span class="property">components</span>, builtInComponents);</span><br></pre></td></tr></table></figure>

<p>下面再来看一下<code>builtInComponents</code>中的内容。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> builtInComponents <span class="keyword">from</span> <span class="string">&quot;../components/index&quot;</span>;</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">KeepAlive</span> <span class="keyword">from</span> <span class="string">&quot;./keep-alive&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="title class_">KeepAlive</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>通过上面的代码可以看到，导出了<code>KeepAlive</code>这个组件</p>
<p>下面看一下<code> initUse(Vue);</code> 该方法的作用:<code>注册Vue.use(),用来注册插件</code></p>
<p><code>initUser</code>方法定义在<code>global-api/use.js</code>文件。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; toArray &#125; <span class="keyword">from</span> <span class="string">&quot;../util/index&quot;</span>;</span><br><span class="line"><span class="comment">//参数为Vue的构造函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initUse</span>(<span class="params">Vue: GlobalAPI</span>) &#123;</span><br><span class="line">  <span class="comment">//为Vue添加use函数</span></span><br><span class="line">  <span class="comment">//plugin:是一个函数或者是对象，表示的就是插件</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">use</span> = <span class="keyword">function</span> (<span class="params">plugin: <span class="built_in">Function</span> | <span class="built_in">Object</span></span>) &#123;</span><br><span class="line">    <span class="comment">//installedPlugins：表示已经安装的插件</span></span><br><span class="line">    <span class="comment">//注意:this表示的是Vue的构造函数</span></span><br><span class="line">    <span class="keyword">const</span> installedPlugins =</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">_installedPlugins</span> || (<span class="variable language_">this</span>.<span class="property">_installedPlugins</span> = []);</span><br><span class="line">    <span class="comment">//判断传递过来的plugin这个插件是否在installedPlugins中存在，如果存在表示已经注册安装了，直接返回</span></span><br><span class="line">    <span class="keyword">if</span> (installedPlugins.<span class="title function_">indexOf</span>(plugin) &gt; -<span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// additional parameters</span></span><br><span class="line">    <span class="keyword">const</span> args = <span class="title function_">toArray</span>(<span class="variable language_">arguments</span>, <span class="number">1</span>);</span><br><span class="line">    args.<span class="title function_">unshift</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="comment">//下面就是实现插件的注册，如果plugin中有install这个属性，表示传递过来的plugin表示的是对象</span></span><br><span class="line">    <span class="comment">//这时候直接调用plugin中的install完成插件的注册。也就是说如果在注册插件的时候，传递的是一个对象，这个对象中一定要有install这个方法，关于这块内容我们在前面的课程中也已经讲解过来。</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> plugin.<span class="property">install</span> === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">      plugin.<span class="property">install</span>.<span class="title function_">apply</span>(plugin, args);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> plugin === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">      <span class="comment">//如果传递的是函数，直接调用函数</span></span><br><span class="line">      plugin.<span class="title function_">apply</span>(<span class="literal">null</span>, args);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//将插件添加的数组中</span></span><br><span class="line">    installedPlugins.<span class="title function_">push</span>(plugin);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面我们再来查看一下<code>initMixin</code>方法，该方法的作用就是用来注册<code>Vue.mixin( )</code>用来实现混入。</p>
<p>具体代码的位置：<code>global-api/mixin.js</code>文件</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; mergeOptions &#125; <span class="keyword">from</span> <span class="string">&quot;../util/index&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initMixin</span>(<span class="params">Vue: GlobalAPI</span>) &#123;</span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property">mixin</span> = <span class="keyword">function</span> (<span class="params">mixin: <span class="built_in">Object</span></span>) &#123;</span><br><span class="line">    <span class="comment">//将mixin这个对象中的所有成员拷贝到options中，this指的就是Vue</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">options</span> = <span class="title function_">mergeOptions</span>(<span class="variable language_">this</span>.<span class="property">options</span>, mixin);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面，我们再来看一下<code>initExtend</code>方法，该方法的作用是注册<code>Vue.extend( )</code>，基于传入的<code>options</code>返回一个组件的构造函数。</p>
<p>代码位置：<code>global-api/entend.js</code></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="property">extend</span> = <span class="keyword">function</span> (<span class="params">extendOptions: <span class="built_in">Object</span></span>): <span class="title class_">Function</span> &#123;</span><br><span class="line">  extendOptions = extendOptions || &#123;&#125;;</span><br><span class="line">  <span class="comment">//Vue的构造函数</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title class_">Super</span> = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="keyword">const</span> <span class="title class_">SuperId</span> = <span class="title class_">Super</span>.<span class="property">cid</span>;</span><br><span class="line">  <span class="comment">//从缓存中加载组件的构造函数</span></span><br><span class="line">  <span class="keyword">const</span> cachedCtors = extendOptions.<span class="property">_Ctor</span> || (extendOptions.<span class="property">_Ctor</span> = &#123;&#125;);</span><br><span class="line">  <span class="keyword">if</span> (cachedCtors[<span class="title class_">SuperId</span>]) &#123;</span><br><span class="line">    <span class="keyword">return</span> cachedCtors[<span class="title class_">SuperId</span>];</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> name = extendOptions.<span class="property">name</span> || <span class="title class_">Super</span>.<span class="property">options</span>.<span class="property">name</span>;</span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; name) &#123;</span><br><span class="line">    <span class="comment">//如果是开发环境验证组件的名称</span></span><br><span class="line">    <span class="title function_">validateComponentName</span>(name);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//VueComponent表示组件的构造函数</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title class_">Sub</span> = <span class="keyword">function</span> <span class="title function_">VueComponent</span>(<span class="params">options</span>) &#123;</span><br><span class="line">    <span class="comment">//调用_init()初始化</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">_init</span>(options);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">//改变了Sub这个构造函数的原型，让其继承了Vue, Super.prototype表示的是Vue的原型。</span></span><br><span class="line">  <span class="comment">//所以说所有的Vue组件都是继承自Vue.</span></span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="title class_">Super</span>.<span class="property"><span class="keyword">prototype</span></span>);</span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">constructor</span> = <span class="title class_">Sub</span>;</span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">cid</span> = cid++;</span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">options</span> = <span class="title function_">mergeOptions</span>(<span class="title class_">Super</span>.<span class="property">options</span>, extendOptions);</span><br><span class="line">  <span class="title class_">Sub</span>[<span class="string">&quot;super&quot;</span>] = <span class="title class_">Super</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// For props and computed properties, we define the proxy getters on</span></span><br><span class="line">  <span class="comment">// the Vue instances at extension time, on the extended prototype. This</span></span><br><span class="line">  <span class="comment">// avoids Object.defineProperty calls for each instance created.</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Sub</span>.<span class="property">options</span>.<span class="property">props</span>) &#123;</span><br><span class="line">    <span class="title function_">initProps</span>(<span class="title class_">Sub</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Sub</span>.<span class="property">options</span>.<span class="property">computed</span>) &#123;</span><br><span class="line">    <span class="title function_">initComputed</span>(<span class="title class_">Sub</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// allow further extension/mixin/plugin usage</span></span><br><span class="line">  <span class="comment">//把Super(Vue)的成员拷贝到Sub这个构造函数中，这样就表明我们创建的组件具有了这些成员。</span></span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">extend</span> = <span class="title class_">Super</span>.<span class="property">extend</span>;</span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">mixin</span> = <span class="title class_">Super</span>.<span class="property">mixin</span>;</span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">use</span> = <span class="title class_">Super</span>.<span class="property">use</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// create asset registers, so extended classes</span></span><br><span class="line">  <span class="comment">// can have their private assets too.</span></span><br><span class="line">  <span class="variable constant_">ASSET_TYPES</span>.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">type</span>) &#123;</span><br><span class="line">    <span class="title class_">Sub</span>[type] = <span class="title class_">Super</span>[type];</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="comment">// enable recursive self-lookup</span></span><br><span class="line">  <span class="keyword">if</span> (name) &#123;</span><br><span class="line">    <span class="title class_">Sub</span>.<span class="property">options</span>.<span class="property">components</span>[name] = <span class="title class_">Sub</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// keep a reference to the super options at extension time.</span></span><br><span class="line">  <span class="comment">// later at instantiation we can check if Super&#x27;s options have</span></span><br><span class="line">  <span class="comment">// been updated.</span></span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">superOptions</span> = <span class="title class_">Super</span>.<span class="property">options</span>;</span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">extendOptions</span> = extendOptions;</span><br><span class="line">  <span class="title class_">Sub</span>.<span class="property">sealedOptions</span> = <span class="title function_">extend</span>(&#123;&#125;, <span class="title class_">Sub</span>.<span class="property">options</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// cache constructor</span></span><br><span class="line">  <span class="comment">//把组件的构造函数缓存到options._Ctor</span></span><br><span class="line">  cachedCtors[<span class="title class_">SuperId</span>] = <span class="title class_">Sub</span>;</span><br><span class="line">  <span class="comment">//返回组件的构造函数VueComponent</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Sub</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>以上内容简单了解一下就可以。</p>
<p>下面看一下：<code>initAssetRegisters</code>方法，在该方法中注册了<code>Vue.directive()</code>,<code>Vue.component( )</code>,<code>Vue.filter( )</code></p>
<p>在这里需要注意的一点就是，以上三个方法并不是一个一个的注册的，而是一起注册的。为什么会一起注册呢？因为这三个方法的参数是一样的。这块可以参考文档。</p>
<p>下面看一下具体的源码实现。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* @flow */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">ASSET_TYPES</span> &#125; <span class="keyword">from</span> <span class="string">&quot;shared/constants&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; isPlainObject, validateComponentName &#125; <span class="keyword">from</span> <span class="string">&quot;../util/index&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initAssetRegisters</span>(<span class="params">Vue: GlobalAPI</span>) &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Create asset registration methods.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="comment">//变量ASSET_TYPES数组，为`Vue`定义相应的方法</span></span><br><span class="line">  <span class="comment">//ASSET_TYPES数组包括了`directive`,`component`,`filter`</span></span><br><span class="line">  <span class="variable constant_">ASSET_TYPES</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">type</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">//分别给Vue中的`directive`,`component`,`filter`注册方法</span></span><br><span class="line">    <span class="title class_">Vue</span>[type] = <span class="keyword">function</span> (<span class="params"></span></span><br><span class="line"><span class="params">      id: string, <span class="comment">//是名字(组件，指令，过滤器的名字)</span></span></span><br><span class="line"><span class="params">      definition: <span class="built_in">Function</span> | <span class="built_in">Object</span> <span class="comment">//定义，可以是对象或者是函数，这两个参数可以通过查看手册：https://vuejs.bootcss.com/api/#Vue-directive</span></span></span><br><span class="line"><span class="params">    </span>): <span class="title class_">Function</span> | <span class="title class_">Object</span> | <span class="keyword">void</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (!definition) &#123;</span><br><span class="line">        <span class="comment">// 如果没有传递第二个参数，通过this.options找到之前存储的directive,component,filter，并返回</span></span><br><span class="line">        <span class="comment">//通过前面的学习，我们知道Vue.directive,Vue.component,Vue.filter都注册到了this.options[&#x27;directives&#x27;],this.options[&#x27;components&#x27;],this.options[&#x27;filters&#x27;]中</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">options</span>[type + <span class="string">&quot;s&quot;</span>][id];</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">        <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; type === <span class="string">&quot;component&quot;</span>) &#123;</span><br><span class="line">          <span class="title function_">validateComponentName</span>(id);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//判断从ASSET_TYPES数组中取出的是否为`component`(也就是是否为组件)</span></span><br><span class="line">        <span class="comment">//同时判断definition参数是否为对象</span></span><br><span class="line">        <span class="keyword">if</span> (type === <span class="string">&quot;component&quot;</span> &amp;&amp; <span class="title function_">isPlainObject</span>(definition)) &#123;</span><br><span class="line">          <span class="comment">//为definition设置名字，如果在Vue.component中的第二个参数设置了name属性，那么就使用该属性的值.</span></span><br><span class="line">          <span class="comment">//如果没有设置，则使用id的值作为definition的名字</span></span><br><span class="line">          definition.<span class="property">name</span> = definition.<span class="property">name</span> || id;</span><br><span class="line">          <span class="comment">//this.options._base表示的是Vue的构造函数。</span></span><br><span class="line">          <span class="comment">//Vue.extend()：我们看过，作用就是将一个普通的对象转换成了VueComponent的构造函数、</span></span><br><span class="line">          <span class="comment">//看到这里，我们回到官方手册:https://vuejs.bootcss.com/api/#Vue-component</span></span><br><span class="line">          <span class="comment">// 注册组件，传入一个扩展过的构造器</span></span><br><span class="line">          <span class="comment">//Vue.component(&#x27;my-component&#x27;, Vue.extend(&#123; /* ... */ &#125;))</span></span><br><span class="line">          <span class="comment">// 注册组件，传入一个选项对象 (自动调用 Vue.extend)</span></span><br><span class="line">          <span class="comment">//Vue.component(&#x27;my-component&#x27;, &#123; /* ... */ &#125;)</span></span><br><span class="line">          <span class="comment">//以上是官方手册中的内容，如果在使用Vue.component方法的时候，传递的第二个参数为Vue.extend,</span></span><br><span class="line">          <span class="comment">//那么会直接执行this.options[type + &#x27;s&#x27;][id] = definition这样代码，因为如果传递的是Vue.extend,那么以上if判断条件不成立。</span></span><br><span class="line">          <span class="comment">// 表示将definition对象的内容存储到this.options中，形式this.options[components][&#x27;my-component&#x27;]</span></span><br><span class="line">          <span class="comment">//如果传递的是一个对象，那么会执行 this.options._base.extend(definition)这行代码。</span></span><br><span class="line">          <span class="comment">//那么现在我们就明白了文档中的这句话的含义:注册组件，传入一个选项对象 (自动调用 Vue.extend)</span></span><br><span class="line"></span><br><span class="line">          definition = <span class="variable language_">this</span>.<span class="property">options</span>.<span class="property">_base</span>.<span class="title function_">extend</span>(definition);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//如果是指令，那么第二个参数可以是可以是对象，也可以是函数。</span></span><br><span class="line">        <span class="comment">//如果是对象，直接执行 this.options[type + &#x27;s&#x27;][id] = definition这行代码</span></span><br><span class="line">        <span class="comment">//如果是函数，会将definition设置给bind与update这两个方法，</span></span><br><span class="line">        <span class="comment">//在官方手册中，有如下内容</span></span><br><span class="line">        <span class="comment">// 注册 (指令函数)</span></span><br><span class="line">        <span class="comment">//Vue.directive(&#x27;my-directive&#x27;, function () &#123;</span></span><br><span class="line">        <span class="comment">// 这里将会被 `bind` 和 `update` 调用</span></span><br><span class="line">        <span class="comment">//&#125;)</span></span><br><span class="line">        <span class="comment">//现在我们能够理解为什么会写`这里将会被 `bind` 和 `update` 调用`这句话了</span></span><br><span class="line">        <span class="keyword">if</span> (type === <span class="string">&quot;directive&quot;</span> &amp;&amp; <span class="keyword">typeof</span> definition === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">          definition = &#123; <span class="attr">bind</span>: definition, <span class="attr">update</span>: definition &#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//最终注册的Vue.component,Vue.filter,Vue.directive都会存储到this.options[&#x27;components&#x27;]</span></span><br><span class="line">        <span class="comment">//this.options[&#x27;filters&#x27;],this.options[&#x27;directives&#x27;]中。是一个全局的注册。</span></span><br><span class="line">        <span class="comment">//Vue.component,Vue.filter,Vue.directive 是全局注册的组件，过滤器，指令</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">options</span>[type + <span class="string">&quot;s&quot;</span>][id] = definition;</span><br><span class="line">        <span class="keyword">return</span> definition;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="9、Vue-实例成员初始化"><a href="#9、Vue-实例成员初始化" class="headerlink" title="9、Vue 实例成员初始化"></a>9、Vue 实例成员初始化</h1><p>通过前面的学习我们知道，实例成员在&#96;&#96;src&#x2F;core&#x2F;instance&#x2F;index.js&#96;文件中，下面我们来看一下该文件中的如下方法内容：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//注册vm的_init( )方法，初始化vm</span></span><br><span class="line"><span class="title function_">initMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//注册vm（Vue实例）的$data/$props/$set/$delete/$watch 属性或方法</span></span><br><span class="line"><span class="title function_">stateMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//初始化事件相关的方法</span></span><br><span class="line"><span class="comment">//$on/$once/$off/$emit</span></span><br><span class="line"><span class="title function_">eventsMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//初始化生命周期相关的混入方法</span></span><br><span class="line"><span class="comment">// $forceUpdate/$destroy</span></span><br><span class="line"><span class="title function_">lifecycleMixin</span>(<span class="title class_">Vue</span>);</span><br><span class="line"><span class="comment">//混入render</span></span><br><span class="line"><span class="comment">// $nextTick</span></span><br><span class="line"><span class="title function_">renderMixin</span>(<span class="title class_">Vue</span>);</span><br></pre></td></tr></table></figure>

<p>这些方法都是以<code>Mixin</code>进行结尾，表示混入的意思，并且传递的参数都是<code>Vue</code>的构造函数，也就是说这些方法都是为<code>Vue</code>混入一些成员。</p>
<p><code>initMixin</code>方法的主要作用就是为<code>Vue</code>实例增加了<code>_init</code>方法，该方法在<code>Vue</code>的构造函数中被调用，该方法也是整个应用的入口。关于<code>_initMixin</code>方法中的代码，后面我们还会详细的讲解。</p>
<p><code>stateMixin</code>方法，具体的代码如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">stateMixin</span>(<span class="params">Vue: Class&lt;Component&gt;</span>) &#123;</span><br><span class="line">  <span class="comment">// flow somehow has problems with directly declared definition object</span></span><br><span class="line">  <span class="comment">// when using Object.defineProperty, so we have to procedurally build up</span></span><br><span class="line">  <span class="comment">// the object here.</span></span><br><span class="line">  <span class="keyword">const</span> dataDef = &#123;&#125;;</span><br><span class="line">  dataDef.<span class="property">get</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">_data</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">const</span> propsDef = &#123;&#125;;</span><br><span class="line">  propsDef.<span class="property">get</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">_props</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">    dataDef.<span class="property">set</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">&quot;Avoid replacing instance root $data. &quot;</span> +</span><br><span class="line">          <span class="string">&quot;Use nested data properties instead.&quot;</span>,</span><br><span class="line">        <span class="variable language_">this</span></span><br><span class="line">      );</span><br><span class="line">    &#125;;</span><br><span class="line">    propsDef.<span class="property">set</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="title function_">warn</span>(<span class="string">`$props is readonly.`</span>, <span class="variable language_">this</span>);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//为Vue的原型上添加`$data`与`$props`属性。也就是完成了`$data`与`$props`的初始化</span></span><br><span class="line">  <span class="comment">//在这里为什么使用Object.defineProperty添加属性呢？</span></span><br><span class="line">  <span class="comment">//原因是：第三个参数，第三个参数就是一个约束。</span></span><br><span class="line">  <span class="comment">//dataDef和propsDef都是对象，并且为其添加了get,在开发环境中添加了set.</span></span><br><span class="line">  <span class="comment">//在访问`$data`或者是`$props`的时候会执行get,如果在开发环境中向`$data`或者是`$props`赋值会执行set，从而给出相应的错误提示信息</span></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>, <span class="string">&quot;$data&quot;</span>, dataDef);</span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>, <span class="string">&quot;$props&quot;</span>, propsDef);</span><br><span class="line"></span><br><span class="line">  <span class="comment">//为Vue的原型上挂在了$set与$delete方法，与Vue.set和Vue.delete是一样的。</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$set</span> = set;</span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$delete</span> = del;</span><br><span class="line">  <span class="comment">//监视数据的变化，该方法后面还会在进行查看。</span></span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$watch</span> = <span class="keyword">function</span> (<span class="params"></span></span><br><span class="line"><span class="params">    expOrFn: string | <span class="built_in">Function</span>,</span></span><br><span class="line"><span class="params">    cb: any,</span></span><br><span class="line"><span class="params">    options?: <span class="built_in">Object</span></span></span><br><span class="line"><span class="params">  </span>): <span class="title class_">Function</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">vm</span>: <span class="title class_">Component</span> = <span class="variable language_">this</span>;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">isPlainObject</span>(cb)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title function_">createWatcher</span>(vm, expOrFn, cb, options);</span><br><span class="line">    &#125;</span><br><span class="line">    options = options || &#123;&#125;;</span><br><span class="line">    options.<span class="property">user</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">const</span> watcher = <span class="keyword">new</span> <span class="title class_">Watcher</span>(vm, expOrFn, cb, options);</span><br><span class="line">    <span class="keyword">if</span> (options.<span class="property">immediate</span>) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        cb.<span class="title function_">call</span>(vm, watcher.<span class="property">value</span>);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="title function_">handleError</span>(</span><br><span class="line">          error,</span><br><span class="line">          vm,</span><br><span class="line">          <span class="string">`callback for immediate watcher &quot;<span class="subst">$&#123;watcher.expression&#125;</span>&quot;`</span></span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span> <span class="title function_">unwatchFn</span>(<span class="params"></span>) &#123;</span><br><span class="line">      watcher.<span class="title function_">teardown</span>();</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面，我们看一下<code>eventsMixin</code>方法。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">//初始化事件相关的方法</span><br><span class="line">//$on/$once/$off/$emit</span><br><span class="line">eventsMixin(Vue);</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">$on:注册事件</span><br><span class="line">$once：注册事件，只能触发一次</span><br><span class="line">$off：取消事件</span><br><span class="line">$emit:是触发事件</span><br></pre></td></tr></table></figure>

<p>下面我们只看一下<code>$on</code>中的代码，其它内容的代码，可以自己查看。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$on</span> = <span class="keyword">function</span> (<span class="params"></span></span><br><span class="line"><span class="params">  event: string | <span class="built_in">Array</span>&lt;string&gt;,</span></span><br><span class="line"><span class="params">  fn: <span class="built_in">Function</span></span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Component</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">vm</span>: <span class="title class_">Component</span> = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="comment">//如果event是一个数组，遍历该数组，给每一个事件，添加对应的处理函数。</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(event)) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = event.<span class="property">length</span>; i &lt; l; i++) &#123;</span><br><span class="line">      vm.$on(event[i], fn);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">//如果是字符串，则根据event(事件名称)从_events对象中查找对应内容，如果没有则指定一个空数组，</span></span><br><span class="line">    <span class="comment">//然后向数组中添加了对应的处理函数。这样每个事件都有了对应的处理函数。</span></span><br><span class="line">    (vm.<span class="property">_events</span>[event] || (vm.<span class="property">_events</span>[event] = [])).<span class="title function_">push</span>(fn);</span><br><span class="line">    <span class="comment">// optimize hook:event cost by using a boolean flag marked at registration</span></span><br><span class="line">    <span class="comment">// instead of a hash lookup</span></span><br><span class="line">    <span class="keyword">if</span> (hookRE.<span class="title function_">test</span>(event)) &#123;</span><br><span class="line">      vm.<span class="property">_hasHookEvent</span> = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> vm;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>下面看一下<code>lifecycleMixin</code>方法：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//初始化生命周期相关的混入方法</span></span><br><span class="line"><span class="comment">//  _update/$forceUpdate/$destroy</span></span><br><span class="line"><span class="title function_">lifecycleMixin</span>(<span class="title class_">Vue</span>);</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// _update方法的作用就是把虚拟DOM转换成真实的DOM</span></span><br><span class="line"><span class="comment">//首次渲染的时候会调用，数据更新会调用</span></span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_update</span> = <span class="keyword">function</span> (<span class="params">vnode: VNode, hydrating?: boolean</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">vm</span>: <span class="title class_">Component</span> = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="keyword">const</span> prevEl = vm.<span class="property">$el</span>;</span><br><span class="line">  <span class="keyword">const</span> prevVnode = vm.<span class="property">_vnode</span>;</span><br><span class="line">  <span class="keyword">const</span> restoreActiveInstance = <span class="title function_">setActiveInstance</span>(vm);</span><br><span class="line">  vm.<span class="property">_vnode</span> = vnode;</span><br><span class="line">  <span class="comment">// Vue.prototype.__patch__ is injected in entry points</span></span><br><span class="line">  <span class="comment">// based on the rendering backend used.</span></span><br><span class="line">  <span class="comment">//首次渲染</span></span><br><span class="line">  <span class="keyword">if</span> (!prevVnode) &#123;</span><br><span class="line">    <span class="comment">// initial render</span></span><br><span class="line">    vm.<span class="property">$el</span> = vm.<span class="title function_">__patch__</span>(vm.<span class="property">$el</span>, vnode, hydrating, <span class="literal">false</span> <span class="comment">/* removeOnly */</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// updates（数据更新）</span></span><br><span class="line">    vm.<span class="property">$el</span> = vm.<span class="title function_">__patch__</span>(prevVnode, vnode);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">restoreActiveInstance</span>();</span><br><span class="line">  <span class="comment">// update __vue__ reference</span></span><br><span class="line">  <span class="keyword">if</span> (prevEl) &#123;</span><br><span class="line">    prevEl.<span class="property">__vue__</span> = <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$el</span>) &#123;</span><br><span class="line">    vm.<span class="property">$el</span>.<span class="property">__vue__</span> = vm;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// if parent is an HOC, update its $el as well</span></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$vnode</span> &amp;&amp; vm.<span class="property">$parent</span> &amp;&amp; vm.<span class="property">$vnode</span> === vm.<span class="property">$parent</span>.<span class="property">_vnode</span>) &#123;</span><br><span class="line">    vm.<span class="property">$parent</span>.<span class="property">$el</span> = vm.<span class="property">$el</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// updated hook is called by the scheduler to ensure that children are</span></span><br><span class="line">  <span class="comment">// updated in a parent&#x27;s updated hook.</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>其它方法，后续再看。</p>
<p>下面看一下<code>renderMixin</code>函数</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//混入render</span></span><br><span class="line"><span class="comment">// $nextTick</span></span><br><span class="line"><span class="title function_">renderMixin</span>(<span class="title class_">Vue</span>);</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">installRenderHelpers</span>(<span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>); <span class="comment">//安装了渲染有关的帮助方法</span></span><br></pre></td></tr></table></figure>

<p><code>installRenderHelpers</code>内部实现：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">installRenderHelpers</span>(<span class="params">target: any</span>) &#123;</span><br><span class="line">  target.<span class="property">_o</span> = markOnce;</span><br><span class="line">  target.<span class="property">_n</span> = toNumber;</span><br><span class="line">  target.<span class="property">_s</span> = toString;</span><br><span class="line">  target.<span class="property">_l</span> = renderList;</span><br><span class="line">  target.<span class="property">_t</span> = renderSlot;</span><br><span class="line">  target.<span class="property">_q</span> = looseEqual;</span><br><span class="line">  target.<span class="property">_i</span> = looseIndexOf;</span><br><span class="line">  target.<span class="property">_m</span> = renderStatic;</span><br><span class="line">  target.<span class="property">_f</span> = resolveFilter;</span><br><span class="line">  target.<span class="property">_k</span> = checkKeyCodes;</span><br><span class="line">  target.<span class="property">_b</span> = bindObjectProps;</span><br><span class="line">  target.<span class="property">_v</span> = createTextVNode; <span class="comment">//创建一个虚拟文本节点</span></span><br><span class="line">  target.<span class="property">_e</span> = createEmptyVNode;</span><br><span class="line">  target.<span class="property">_u</span> = resolveScopedSlots;</span><br><span class="line">  target.<span class="property">_g</span> = bindObjectListeners;</span><br><span class="line">  target.<span class="property">_d</span> = bindDynamicKeys;</span><br><span class="line">  target.<span class="property">_p</span> = prependModifier;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>以上这些函数都是在编译的时候会用到，也就是将<code>template</code>模板编译成<code>render</code>函数的时候。</p>
<p>在<code>installRenderHelpers</code> 创建了<code>$nextTick</code>,关于这块内容，我们后期再来查看</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$nextTick</span> = <span class="keyword">function</span> (<span class="params">fn: <span class="built_in">Function</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">nextTick</span>(fn, <span class="variable language_">this</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>下面有一个<code>_render</code>方法，在该方法中有一个非常重要的代码。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vnode = render.<span class="title function_">call</span>(vm.<span class="property">_renderProxy</span>, vm.<span class="property">$createElement</span>);</span><br></pre></td></tr></table></figure>

<p>在上面的代码中首先看一下<code>render</code>的定义：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; render, _parentVnode &#125; = vm.<span class="property">$options</span>;</span><br></pre></td></tr></table></figure>

<p>从上面这行代码中，我们知道了<code>render</code>来自于<code>vm.$options</code>,那么就表明这个<code>render</code>是在用户创建<code>Vue</code>的实例的时候执行的的<code>render</code>.</p>
<p><code>render.call</code>范围<code>render</code>方法的调用，第一个参数改变<code>this</code>的指向，第二个参数：<code> vm.$createElement</code>其实就是我们在创建<code>Vue</code>的实例的时候，指定的<code>h</code>函数。<code>h</code>函数的作用就是创建虚拟<code>DOM</code></p>
<p>以上就是我们这一小节讲解的内容，当然其内部还有一些其它方法，这些我们会在后面在继续查看。</p>
<h1 id="10、init-方法"><a href="#10、init-方法" class="headerlink" title="10、init 方法"></a>10、init 方法</h1><p>在&#96;&#96;src&#x2F;core&#x2F;instance&#x2F;index.js<code>文件中,创建了</code>Vue<code>的构造函数，并且在其内部调用了</code>_init( )方法<code>,而该方法的初始化是在</code>initMixin(Vue);<code>方法中完成的，下面我们再来看一下该方法的实现。</code>_init( )&#96;方法完成了初始化的工作，所以我们重点看一下该方法中初始化了哪些内容？</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initMixin</span>(<span class="params">Vue: Class&lt;Component&gt;</span>) &#123;</span><br><span class="line">  <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_init</span> = <span class="keyword">function</span> (<span class="params">options?: <span class="built_in">Object</span></span>) &#123;</span><br><span class="line">    <span class="comment">//Vue的实例</span></span><br><span class="line">    <span class="keyword">const</span> <span class="attr">vm</span>: <span class="title class_">Component</span> = <span class="variable language_">this</span>;</span><br><span class="line">    <span class="comment">// a uid</span></span><br><span class="line">    <span class="comment">//唯一标识</span></span><br><span class="line">    vm.<span class="property">_uid</span> = uid++;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> startTag, endTag;</span><br><span class="line">    <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">      startTag = <span class="string">`vue-perf-start:<span class="subst">$&#123;vm._uid&#125;</span>`</span>;</span><br><span class="line">      endTag = <span class="string">`vue-perf-end:<span class="subst">$&#123;vm._uid&#125;</span>`</span>;</span><br><span class="line">      <span class="title function_">mark</span>(startTag);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// a flag to avoid this being observed</span></span><br><span class="line">    <span class="comment">//如果是Vue实例，不需要被observe处理。</span></span><br><span class="line">    vm.<span class="property">_isVue</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// merge options</span></span><br><span class="line">    <span class="comment">// 合并options</span></span><br><span class="line">    <span class="keyword">if</span> (options &amp;&amp; options.<span class="property">_isComponent</span>) &#123;</span><br><span class="line">      <span class="comment">// optimize internal component instantiation</span></span><br><span class="line">      <span class="comment">// since dynamic options merging is pretty slow, and none of the</span></span><br><span class="line">      <span class="comment">// internal component options needs special treatment.</span></span><br><span class="line">      <span class="title function_">initInternalComponent</span>(vm, options);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">//将用户掺入的options与Vue构造函数中的options进行合并、</span></span><br><span class="line">      <span class="comment">//在初始化静态成员的时候已经为Vue构造函数初始化了v-show,v-model,keep-alive等指令和组件</span></span><br><span class="line">      vm.<span class="property">$options</span> = <span class="title function_">mergeOptions</span>(</span><br><span class="line">        <span class="title function_">resolveConstructorOptions</span>(vm.<span class="property">constructor</span>),</span><br><span class="line">        options || &#123;&#125;,</span><br><span class="line">        vm</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* istanbul ignore else */</span></span><br><span class="line">    <span class="comment">//设置渲染的代理对象</span></span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">      <span class="comment">//开发环境调用initProxy方法</span></span><br><span class="line">      <span class="title function_">initProxy</span>(vm);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">//渲染的时候设置的代理对象就是Vue的实例</span></span><br><span class="line">      <span class="comment">//在渲染的时候会用到该属性。</span></span><br><span class="line">      vm.<span class="property">_renderProxy</span> = vm;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// expose real self</span></span><br><span class="line">    vm.<span class="property">_self</span> = vm;</span><br><span class="line">    <span class="comment">//下面的函数是完成Vue实例的一些初始化操作。</span></span><br><span class="line">    <span class="comment">//初始与生命周期相关的属性（$children,$parent,$root,$refs）</span></span><br><span class="line">    <span class="title function_">initLifecycle</span>(vm);</span><br><span class="line">    <span class="comment">//初始化当前组件的事件</span></span><br><span class="line">    <span class="title function_">initEvents</span>(vm);</span><br><span class="line">    <span class="comment">//初始化了render中所使用的h函数</span></span><br><span class="line">    <span class="comment">//同时还初始化了$slots/$attrs 等等</span></span><br><span class="line">    <span class="comment">//在initRender方法中，注意如下两行代码</span></span><br><span class="line">    <span class="comment">//   vm._c = (a, b, c, d) =&gt; createElement(vm, a, b, c, d, false)</span></span><br><span class="line">    <span class="comment">//vm.$createElement = (a, b, c, d) =&gt; createElement(vm, a, b, c, d, true)</span></span><br><span class="line">    <span class="comment">//当我们在创建一个Vue实例的时候，是可以直接传递一个render函数，render函数需要一个参数就是h函数，</span></span><br><span class="line">    <span class="comment">//$createElement就是传递过来的h函数。其作用就是将虚拟DOM转换成真实DOM</span></span><br><span class="line">    <span class="comment">//当我们把template编译成render函数的时候，在内部调用的是_c这个函数，在模板编译的过程中，会看到这个方法</span></span><br><span class="line">    <span class="comment">// 而$createElement函数，是在new Vue实例的时候传递的render函数所调用的。</span></span><br><span class="line">    <span class="title function_">initRender</span>(vm);</span><br><span class="line">    <span class="comment">//触发生命周期中的beforeCreate钩子函数</span></span><br><span class="line">    <span class="title function_">callHook</span>(vm, <span class="string">&quot;beforeCreate&quot;</span>);</span><br><span class="line">    <span class="comment">//初始化inject，把inject的成员注入到Vue的实例上</span></span><br><span class="line">    <span class="title function_">initInjections</span>(vm); <span class="comment">// resolve injections before data/props</span></span><br><span class="line">    <span class="comment">//初始化Vue实例中的methods/computed/watch等，关于该函数会在下一小节中进行讲解</span></span><br><span class="line">    <span class="title function_">initState</span>(vm);</span><br><span class="line">    <span class="comment">// 初始化provide</span></span><br><span class="line">    <span class="title function_">initProvide</span>(vm); <span class="comment">// resolve provide after data/props</span></span><br><span class="line">    <span class="comment">//触发生命周期中的created钩子函数</span></span><br><span class="line">    <span class="title function_">callHook</span>(vm, <span class="string">&quot;created&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">      vm.<span class="property">_name</span> = <span class="title function_">formatComponentName</span>(vm, <span class="literal">false</span>);</span><br><span class="line">      <span class="title function_">mark</span>(endTag);</span><br><span class="line">      <span class="title function_">measure</span>(<span class="string">`vue <span class="subst">$&#123;vm._name&#125;</span> init`</span>, startTag, endTag);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//调用$mount挂载整个页面，并且进行页面的渲染</span></span><br><span class="line">    <span class="keyword">if</span> (vm.<span class="property">$options</span>.<span class="property">el</span>) &#123;</span><br><span class="line">      vm.$mount(vm.<span class="property">$options</span>.<span class="property">el</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>initProxy</code>方法的实现</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">onst hasProxy =</span><br><span class="line">    <span class="keyword">typeof</span> <span class="title class_">Proxy</span> !== <span class="string">&#x27;undefined&#x27;</span> &amp;&amp; <span class="title function_">isNative</span>(<span class="title class_">Proxy</span>)</span><br><span class="line"><span class="comment">//如果有Proxy，通过new Proxy来创建一个代理。</span></span><br><span class="line">  <span class="keyword">if</span> (hasProxy) &#123;</span><br><span class="line">    <span class="keyword">const</span> isBuiltInModifier = <span class="title function_">makeMap</span>(<span class="string">&#x27;stop,prevent,self,ctrl,shift,alt,meta,exact&#x27;</span>)</span><br><span class="line">    config.<span class="property">keyCodes</span> = <span class="keyword">new</span> <span class="title class_">Proxy</span>(config.<span class="property">keyCodes</span>, &#123;</span><br><span class="line">      set (target, key, value) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="title function_">isBuiltInModifier</span>(key)) &#123;</span><br><span class="line">          <span class="title function_">warn</span>(<span class="string">`Avoid overwriting built-in modifier in config.keyCodes: .<span class="subst">$&#123;key&#125;</span>`</span>)</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          target[key] = value</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<h1 id="11、initState-方法"><a href="#11、initState-方法" class="headerlink" title="11、initState 方法"></a>11、initState 方法</h1><p><code>initState</code>方法的作用就是：初始化<code>Vue</code>实例中的<code>methods/computed/watch</code>等.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initState</span>(<span class="params">vm: Component</span>) &#123;</span><br><span class="line">  vm.<span class="property">_watchers</span> = [];</span><br><span class="line">  <span class="comment">//获取options</span></span><br><span class="line">  <span class="keyword">const</span> opts = vm.<span class="property">$options</span>;</span><br><span class="line">  <span class="comment">//初始化props,并且注入到Vue实例中</span></span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">props</span>) <span class="title function_">initProps</span>(vm, opts.<span class="property">props</span>);</span><br><span class="line">  <span class="comment">//初始化methods，把methods中的方法注册到Vue实例中，下面看一下initMethods方法的内部实现</span></span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">methods</span>) <span class="title function_">initMethods</span>(vm, opts.<span class="property">methods</span>);</span><br><span class="line">  <span class="comment">//如果options中有data属性会调用initData方法，下面查看一下initData方法内部实现</span></span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">data</span>) &#123;</span><br><span class="line">    <span class="title function_">initData</span>(vm); <span class="comment">//初始化data</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">//如果没有data属性，会为Vue的实例创建一个空对象，并且将其修改成响应式的。关于响应式的内容我们后期还会查看。</span></span><br><span class="line">    <span class="title function_">observe</span>((vm.<span class="property">_data</span> = &#123;&#125;), <span class="literal">true</span> <span class="comment">/* asRootData */</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//初始化computed，会把computed注册到Vue的实例中，可以自己查看源码</span></span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">computed</span>) <span class="title function_">initComputed</span>(vm, opts.<span class="property">computed</span>);</span><br><span class="line">  <span class="comment">//初始化watch，会把watch注册到Vue的实例中，可以自己查看源码</span></span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">watch</span> &amp;&amp; opts.<span class="property">watch</span> !== nativeWatch) &#123;</span><br><span class="line">    <span class="title function_">initWatch</span>(vm, opts.<span class="property">watch</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>initMethods</code>方法实现</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">initMethods</span>(<span class="params">vm: Component, methods: <span class="built_in">Object</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> props = vm.<span class="property">$options</span>.<span class="property">props</span>; <span class="comment">//获取props</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> methods) &#123;</span><br><span class="line">    <span class="comment">//对所有的methods进行遍历</span></span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">      <span class="comment">//在开发环境中</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> methods[key] !== <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">        <span class="comment">//获取对应的method如果不是函数，会给出相应的警告</span></span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">`Method &quot;<span class="subst">$&#123;key&#125;</span>&quot; has type &quot;<span class="subst">$&#123;<span class="keyword">typeof</span> methods[</span></span></span><br><span class="line"><span class="subst"><span class="string">            key</span></span></span><br><span class="line"><span class="subst"><span class="string">          ]&#125;</span>&quot; in the component definition. `</span> +</span><br><span class="line">            <span class="string">`Did you reference the function correctly?`</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 如果method的名字与props中的属性名字重名也会给出相应的警告</span></span><br><span class="line">      <span class="keyword">if</span> (props &amp;&amp; <span class="title function_">hasOwn</span>(props, key)) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(<span class="string">`Method &quot;<span class="subst">$&#123;key&#125;</span>&quot; has already been defined as a prop.`</span>, vm);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//判断方法的名称是否为Vue的实例，同时判断方法的名称是否以_或者是$开头。</span></span><br><span class="line">      <span class="comment">//如果以 _ 开头表示一个私有属性，所以不建议方法名称以 _ 开头。</span></span><br><span class="line">      <span class="comment">// 以$开头的都是成员都是Vue的成员，所以也不建议方法名称使用$开头</span></span><br><span class="line">      <span class="keyword">if</span> (key <span class="keyword">in</span> vm &amp;&amp; <span class="title function_">isReserved</span>(key)) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">`Method &quot;<span class="subst">$&#123;key&#125;</span>&quot; conflicts with an existing Vue instance method. `</span> +</span><br><span class="line">            <span class="string">`Avoid defining component methods that start with _ or $.`</span></span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//把method注册到Vue的实例中</span></span><br><span class="line">    <span class="comment">//首先判断从methods中取出来的方法如果不是&quot;function&quot;,返回noop,也就是一个空函数。</span></span><br><span class="line">    <span class="comment">//如果是&quot;funciton&quot;，返回该函数，同时通过bind修改函数内部this的指向，然后指向到Vue的实例。</span></span><br><span class="line">    vm[key] =</span><br><span class="line">      <span class="keyword">typeof</span> methods[key] !== <span class="string">&quot;function&quot;</span> ? noop : <span class="title function_">bind</span>(methods[key], vm);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>initData</code>方法实现</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">initData</span>(<span class="params">vm: Component</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> data = vm.<span class="property">$options</span>.<span class="property">data</span>; <span class="comment">//获取props中的data内容</span></span><br><span class="line">  <span class="comment">//初始化_data,判断data的类型是不是一个函数如果不是直接返回data，如果是调用getData方法,getData中就是通过call来调用data函数。</span></span><br><span class="line">  <span class="comment">//其实就是初始化组件中的data,组件中的data就是一个函数，如果是Vue实例中的data,那么就是一个对象。</span></span><br><span class="line">  data = vm.<span class="property">_data</span> = <span class="keyword">typeof</span> data === <span class="string">&quot;function&quot;</span> ? <span class="title function_">getData</span>(data, vm) : data || &#123;&#125;;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="title function_">isPlainObject</span>(data)) &#123;</span><br><span class="line">    data = &#123;&#125;;</span><br><span class="line">    process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp;</span><br><span class="line">      <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">&quot;data functions should return an object:\n&quot;</span> +</span><br><span class="line">          <span class="string">&quot;https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function&quot;</span>,</span><br><span class="line">        vm</span><br><span class="line">      );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// proxy data on instance</span></span><br><span class="line">  <span class="comment">//获取data中的所有属性</span></span><br><span class="line">  <span class="keyword">const</span> keys = <span class="title class_">Object</span>.<span class="title function_">keys</span>(data);</span><br><span class="line">  <span class="comment">// 获取props</span></span><br><span class="line">  <span class="keyword">const</span> props = vm.<span class="property">$options</span>.<span class="property">props</span>;</span><br><span class="line">  <span class="comment">// 获取methods</span></span><br><span class="line">  <span class="keyword">const</span> methods = vm.<span class="property">$options</span>.<span class="property">methods</span>;</span><br><span class="line">  <span class="keyword">let</span> i = keys.<span class="property">length</span>;</span><br><span class="line">  <span class="comment">//判断data中的成员是否和`props/methods`重名。在开发环境中有重名会出现相应的警告信息。</span></span><br><span class="line">  <span class="keyword">while</span> (i--) &#123;</span><br><span class="line">    <span class="keyword">const</span> key = keys[i];</span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (methods &amp;&amp; <span class="title function_">hasOwn</span>(methods, key)) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">`Method &quot;<span class="subst">$&#123;key&#125;</span>&quot; has already been defined as a data property.`</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (props &amp;&amp; <span class="title function_">hasOwn</span>(props, key)) &#123;</span><br><span class="line">      process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">`The data property &quot;<span class="subst">$&#123;key&#125;</span>&quot; is already declared as a prop. `</span> +</span><br><span class="line">            <span class="string">`Use prop default value instead.`</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!<span class="title function_">isReserved</span>(key)) &#123;</span><br><span class="line">      <span class="comment">//isReserved方法就是判断当前的属性是否以`_`和`$`开头，如果是以`_`和`$`开头就不会把当前的属性注入到Vue实例中。否则会将当前属性（key的值）注册到Vue的实例中。</span></span><br><span class="line">      <span class="title function_">proxy</span>(vm, <span class="string">`_data`</span>, key);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// observe data</span></span><br><span class="line">  <span class="comment">//对data做响应式的处理。</span></span><br><span class="line">  <span class="title function_">observe</span>(data, <span class="literal">true</span> <span class="comment">/* asRootData */</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面可以看一下<code>proxy</code>方法中的代码。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">proxy</span>(<span class="params">target: <span class="built_in">Object</span>, sourceKey: string, key: string</span>) &#123;</span><br><span class="line">  <span class="comment">// 如果访问get，那么返回的就是this._data中当前属性的值</span></span><br><span class="line">  sharedPropertyDefinition.<span class="property">get</span> = <span class="keyword">function</span> <span class="title function_">proxyGetter</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>[sourceKey][key];</span><br><span class="line">  &#125;;</span><br><span class="line">  sharedPropertyDefinition.<span class="property">set</span> = <span class="keyword">function</span> <span class="title function_">proxySetter</span>(<span class="params">val</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>[sourceKey][key] = val;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="comment">//把当前属性注入到Vue实例中</span></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(target, key, sharedPropertyDefinition);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="12、总结"><a href="#12、总结" class="headerlink" title="12、总结"></a>12、总结</h1><p>下面我们把上面讲解的内容，做一个总结：</p>
<p>在首次渲染的时候，首先对<code>Vue</code>进行初始化，同时完成实例成员与静态成员的初始化。初始化完成后会执行构造函数，在构造函数中调用了<code>_init</code>方法，该方法是整个<code>Vue</code>的入口，在该方法中调用了<code>vm.$mount</code>方法，通过前面的学习，我们知道有两个<code>$mount</code>, 首先第一个<code>$mount</code>来自于<code>src/platforms/web/entry-runtime-with-compiler.js</code>文件，这个<code>$mount</code>方法的作用就是把模板编译成<code>render</code>函数，当然，在把模板编译成<code>render</code>函数之前，先判断一下是否传入了<code>render</code>这个选项，如果没有传入，这时就会去获取<code>template</code>选项，如果也没有<code>template</code>这个选项，那么会把<code>el</code>中的内容作为模板，然后把模板编译成<code>render</code>函数。这里是通过<code>compileToFunctions</code>这个函数，把模板编译成<code>render</code>函数。把编译好的<code>render</code>函数存储到<code>options.render</code>中。</p>
<p>下面会调用<code>src/platforms/web/runtime/index.js</code>文件中的<code>$mount</code>方法。在这个方法中会重新获取<code>el</code>,因为运行时版本，是不会执行<code>src/platforms/web/entry-runtime-with-compiler.js</code>文件中的代码，所以这里需要重新获取<code>el</code>,下面调用<code>mountComponent( )</code>方法，该方法定义的文件为<code>src/core/instance/lifecycle.js</code>,在<code>mountComponent( )</code>方法中，首先判断是否有<code>render</code>选项，如果没有但是传入了模板，并且当前是开发环境，那么会打印一个警告信息。警告信息为运行时版本不支持编译器。下面触发了<code>beforeMount</code>这个钩子函数，然后定义<code>updateComponent</code>,在<code>updateComponent</code>中，调用了<code>vm._render( )</code>函数和<code>vm._update</code>函数。</p>
<p><code>vm._render()</code>函数的作用就是生成虚拟<code>DOM</code>（在 <code>vm._render()</code>这个方法中，调用了在创建<code>Vue</code>实例的时候传入的<code>render</code>函数,或者是将<code>template</code>编译成的<code>render</code>函数）,<code>vm._update( )</code>函数的作用就是将虚拟<code>DOM</code>转换成真实的<code>DOM</code>（在<code>vm._udpate</code>这个方法中调用了<code>vm.__patch__</code>方法将虚拟<code>DOM</code>转换成了真实的<code>DOM</code>然后挂在到页面中）.接下来创建了<code>Watcher</code>的实例，在<code>Watcher</code>实例中调用了<code>updateComponent</code>, 接下来会执行<code>mounted</code>这个钩子函数，完成挂在，最后返回<code>Vue</code>的实例。</p>
<h1 id="13、响应式处理入口"><a href="#13、响应式处理入口" class="headerlink" title="13、响应式处理入口"></a>13、响应式处理入口</h1><p>通过查看 源码解决如下的问题</p>
<ul>
<li><code>vm.msg=&#123;count:0&#125;</code> 重新给属性赋值，是否是响应式的？</li>
<li><code>vm.arr[0]=4</code> 给数组元素赋值，视图是否会更新</li>
<li><code>vm.arr.length=0</code> 修改数组的<code>length</code>,视图是否会更新</li>
<li><code>vm.arr.push(5)</code> 视图是否会更新</li>
</ul>
<p>响应式处理的入口</p>
<p>整个响应式处理的过程是比较复杂的，下面我们先查看<code>src/core/instance/init.js</code>文件,在该文件中有<code>initState(vm)</code>方法，该方法完成了<code>vm</code>状态的初始化，初始化了<code>_data</code>,<code>_props</code>,<code>methods</code>等。</p>
<p>然后我们再来看一下，<code>src/core/instance/state.js</code></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//数据的初始化</span></span><br><span class="line"><span class="keyword">if</span> (opts.<span class="property">data</span>) &#123;</span><br><span class="line">  <span class="title function_">initData</span>(vm); <span class="comment">//把data中的成员注入到Vue实例，并且转换成响应式的对象</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="comment">//如果options选项中没有data，这里会将data初始化一个空对象。传入到observe这个方法中转换成响应式的对象</span></span><br><span class="line">  <span class="title function_">observe</span>((vm.<span class="property">_data</span> = &#123;&#125;), <span class="literal">true</span>); <span class="comment">//observe就是响应式的入口。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面看一下<code>src/core/instance/init.js</code>文件，在该文件中找到<code>initState(vm)</code>方法，该方法就是注册<code>vm</code>（<code>Vue</code>实例）的<code>$data/$props/$set/$delete/$watch</code> 属性或方法.当我们单击进入该方法后，可以看到该方法所在的文件为<code>src/core/instance/state.js</code>.</p>
<p>在该文件中找到如下代码</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (opts.<span class="property">data</span>) &#123;</span><br><span class="line">  <span class="title function_">initData</span>(vm);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="title function_">observe</span>((vm.<span class="property">_data</span> = &#123;&#125;), <span class="literal">true</span> <span class="comment">/* asRootData */</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面在进入<code>initData</code>方法，代码如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">initData</span>(<span class="params">vm: Component</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> data = vm.<span class="property">$options</span>.<span class="property">data</span>; <span class="comment">//获取props中的data内容</span></span><br><span class="line">  <span class="comment">//初始化_data,判断data的类型是不是一个函数如果不是直接返回data对象，如果是调用getData方法,getData中就是通过call来调用data函数。</span></span><br><span class="line">  <span class="comment">//其实就是初始化组件中的data,组件中的data就是一个函数，如果是Vue实例中的data,那么就是一个对象。</span></span><br><span class="line">  data = vm.<span class="property">_data</span> = <span class="keyword">typeof</span> data === <span class="string">&quot;function&quot;</span> ? <span class="title function_">getData</span>(data, vm) : data || &#123;&#125;;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="title function_">isPlainObject</span>(data)) &#123;</span><br><span class="line">    data = &#123;&#125;;</span><br><span class="line">    process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp;</span><br><span class="line">      <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">&quot;data functions should return an object:\n&quot;</span> +</span><br><span class="line">          <span class="string">&quot;https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function&quot;</span>,</span><br><span class="line">        vm</span><br><span class="line">      );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// proxy data on instance</span></span><br><span class="line">  <span class="comment">//获取data中的所有属性</span></span><br><span class="line">  <span class="keyword">const</span> keys = <span class="title class_">Object</span>.<span class="title function_">keys</span>(data);</span><br><span class="line">  <span class="comment">// 获取props</span></span><br><span class="line">  <span class="keyword">const</span> props = vm.<span class="property">$options</span>.<span class="property">props</span>;</span><br><span class="line">  <span class="comment">// 获取methods</span></span><br><span class="line">  <span class="keyword">const</span> methods = vm.<span class="property">$options</span>.<span class="property">methods</span>;</span><br><span class="line">  <span class="keyword">let</span> i = keys.<span class="property">length</span>;</span><br><span class="line">  <span class="comment">//判断data中的成员是否和`props/methods`重名。在开发环境中有重名会出现相应的警告信息。</span></span><br><span class="line">  <span class="keyword">while</span> (i--) &#123;</span><br><span class="line">    <span class="keyword">const</span> key = keys[i];</span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (methods &amp;&amp; <span class="title function_">hasOwn</span>(methods, key)) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">`Method &quot;<span class="subst">$&#123;key&#125;</span>&quot; has already been defined as a data property.`</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (props &amp;&amp; <span class="title function_">hasOwn</span>(props, key)) &#123;</span><br><span class="line">      process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">`The data property &quot;<span class="subst">$&#123;key&#125;</span>&quot; is already declared as a prop. `</span> +</span><br><span class="line">            <span class="string">`Use prop default value instead.`</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!<span class="title function_">isReserved</span>(key)) &#123;</span><br><span class="line">      <span class="comment">//isReserved方法就是判断当前的属性是否以`_`和`$`开头，如果是以`_`和`$`开头就不会把当前的属性注入到Vue实例中。否则会将当前属性（key的值）注册到Vue的实例中。</span></span><br><span class="line">      <span class="title function_">proxy</span>(vm, <span class="string">`_data`</span>, key);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// observe data</span></span><br><span class="line">  <span class="comment">//对data做响应式的处理。</span></span><br><span class="line">  <span class="title function_">observe</span>(data, <span class="literal">true</span> <span class="comment">/* asRootData */</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的代码中，我们可以看到最后调用了<code>observe</code>方法。<code>data</code>就是传递过来的<code>options</code>选项中的<code>data</code>,第二个参数为<code>true</code>,表示的就是根数据,根数据会做相应的处理。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Attempt to create an observer instance for a value,</span></span><br><span class="line"><span class="comment"> * returns the new observer if successfully observed,</span></span><br><span class="line"><span class="comment"> * or the existing observer if the value already has one.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">//试图为value创建一个Observer对象,如果创建成功，将创建的Observer返回，或者返回一个已经存在的Observer对象。也就是说valu这个参数如果已经有Observer对象，直接返回。</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">observe</span>(<span class="params">value: any, asRootData: ?boolean</span>): <span class="title class_">Observer</span> | <span class="keyword">void</span> &#123;</span><br><span class="line">  <span class="comment">// 判断 value 是否是对象,如果不是对象，或者是VNode，直接返回，不做响应式的处理</span></span><br><span class="line">  <span class="keyword">if</span> (!<span class="title function_">isObject</span>(value) || value <span class="keyword">instanceof</span> <span class="title class_">VNode</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">let</span> <span class="attr">ob</span>: <span class="title class_">Observer</span> | <span class="keyword">void</span>; <span class="comment">//声明一个Observer类型的变量</span></span><br><span class="line">  <span class="comment">// 判断value中是否有&#x27;__ob__&#x27;这个属性，如果有，那么就需要判断value中的ob这个属性是否为Observer的实例</span></span><br><span class="line">  <span class="comment">//如果value中的ob属性是Observer的实例，在这就赋值给ob这个变量。最后直接返回ob.</span></span><br><span class="line">  <span class="comment">//这一点就和最开始我们说的是一样的，也是如果已经存在Observer对象，直接返回,相当于做了一个缓存的效果。</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">hasOwn</span>(value, <span class="string">&quot;__ob__&quot;</span>) &amp;&amp; value.<span class="property">__ob__</span> <span class="keyword">instanceof</span> <span class="title class_">Observer</span>) &#123;</span><br><span class="line">    ob = value.<span class="property">__ob__</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (</span><br><span class="line">    <span class="comment">//如果value中没有ob这个属性，那么就需要创建一个Observer对象。</span></span><br><span class="line">    <span class="comment">//在创建Observer对象之前，需要做一些判断的处理。</span></span><br><span class="line">    <span class="comment">//这里我们重点看一下,如下的判断</span></span><br><span class="line">    <span class="comment">//(Array.isArray(value) || isPlainObject(value)) &amp;&amp;</span></span><br><span class="line">    <span class="comment">//Object.isExtensible(value) &amp;&amp;</span></span><br><span class="line">    <span class="comment">//!value._isVue</span></span><br><span class="line">    <span class="comment">//(Array.isArray(value):判断传递过来的value是否为一个数组</span></span><br><span class="line">    <span class="comment">//isPlainObject(value)):判断value是否为一个对象。</span></span><br><span class="line">    <span class="comment">//!value._isVue:判断value是否为一个Vue的实例，在core/instance/index.js文件中，调用了initMixin方法，</span></span><br><span class="line">    <span class="comment">//在该方法中，设置了_isVue这个属性，如果传递过来的的value是Vue的实例就不要通过Observer设置响应式。</span></span><br><span class="line">    <span class="comment">//如果value可以进行响应式的处理，就需要创建一个Observer对象。</span></span><br><span class="line">    shouldObserve &amp;&amp;</span><br><span class="line">    !<span class="title function_">isServerRendering</span>() &amp;&amp;</span><br><span class="line">    (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(value) || <span class="title function_">isPlainObject</span>(value)) &amp;&amp;</span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">isExtensible</span>(value) &amp;&amp;</span><br><span class="line">    !value.<span class="property">_isVue</span></span><br><span class="line">  ) &#123;</span><br><span class="line">    <span class="comment">// 创建一个 Observer 对象</span></span><br><span class="line">    <span class="comment">//在Observer中把value中的所有属性转换成get与set的形式。</span></span><br><span class="line">    ob = <span class="keyword">new</span> <span class="title class_">Observer</span>(value);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (asRootData &amp;&amp; ob) &#123;</span><br><span class="line">    ob.<span class="property">vmCount</span>++;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> ob;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里最重要的就是<code>Observer</code>对象的创建，下一小节我们来看一下<code>Observer</code>中是怎样处理的。</p>
<h1 id="14、Observer"><a href="#14、Observer" class="headerlink" title="14、Observer"></a>14、Observer</h1><p>在上一小节中，我们已经找到了响应式的入口，<code>Observer</code>.（<code>src/core/observer/index.js</code>）</p>
<p>下面我们看一下<code>Observer</code>对应的代码。<code>Observer</code>是一个类</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Observer class that is attached to each observed</span></span><br><span class="line"><span class="comment"> * object. Once attached, the observer converts the target</span></span><br><span class="line"><span class="comment"> * object&#x27;s property keys into getter/setters that</span></span><br><span class="line"><span class="comment"> * collect dependencies and dispatch updates.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">//Observer类附加到每个被观察的对象，一旦附加，observer就会转换目标对象的所有属性，将其转换成`getter/setter`.</span></span><br><span class="line"><span class="comment">//其目的就是收集依赖和派发更新。其实就是我们前面所讲的发送通知。</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Observer</span> &#123;</span><br><span class="line">  <span class="comment">// 观察对象</span></span><br><span class="line">  <span class="attr">value</span>: any;</span><br><span class="line">  <span class="comment">// 依赖对象</span></span><br><span class="line">  <span class="attr">dep</span>: <span class="title class_">Dep</span>;</span><br><span class="line">  <span class="comment">// 实例计数器</span></span><br><span class="line">  <span class="attr">vmCount</span>: number; <span class="comment">// number of vms that have this object as root $data</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span> (<span class="attr">value</span>: any) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">value</span> = value</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">dep</span> = <span class="keyword">new</span> <span class="title class_">Dep</span>()</span><br><span class="line">    <span class="comment">// 初始化实例的 vmCount 为0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vmCount</span> = <span class="number">0</span></span><br><span class="line">    <span class="comment">// 将实例挂载到观察对象的 __ob__ 属性上。</span></span><br><span class="line">    <span class="title function_">def</span>(value, <span class="string">&#x27;__ob__&#x27;</span>, <span class="variable language_">this</span>)</span><br><span class="line">    <span class="comment">// 数组的响应式处理,后面再看具体的实现</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(value)) &#123;</span><br><span class="line">      <span class="keyword">if</span> (hasProto) &#123;</span><br><span class="line">        <span class="title function_">protoAugment</span>(value, arrayMethods)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">copyAugment</span>(value, arrayMethods, arrayKeys)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 为数组中的每一个对象创建一个 observer 实例</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">observeArray</span>(value)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 遍历对象中的每一个属性，转换成 setter/getter</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">walk</span>(value)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>下面 看一下对应的<code>walk</code>方法的实现。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">walk (<span class="attr">obj</span>: <span class="title class_">Object</span>) &#123;</span><br><span class="line">  <span class="comment">// 获取观察对象的每一个属性</span></span><br><span class="line">  <span class="keyword">const</span> keys = <span class="title class_">Object</span>.<span class="title function_">keys</span>(obj)</span><br><span class="line">  <span class="comment">// 遍历每一个属性，设置为响应式数据</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; keys.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="comment">//该方法就是将对象中的属性转换成getter和setter.</span></span><br><span class="line">      <span class="comment">//当然在将属性转换成getter/setter前，也做了其它的一些处理，例如收集依赖，当数据发生变化后，发送通知等。</span></span><br><span class="line">      <span class="comment">//下一小节，查看该方法的实现。</span></span><br><span class="line">    <span class="title function_">defineReactive</span>(obj, keys[i])</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>最好还调用了<code>observeArray</code>方法，该方法的作用就是将数组转换成响应式的。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">observeArray (<span class="attr">items</span>: <span class="title class_">Array</span>&lt;any&gt;) &#123;</span><br><span class="line">   <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = items.<span class="property">length</span>; i &lt; l; i++) &#123;</span><br><span class="line">     <span class="title function_">observe</span>(items[i])</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p><code>Observer</code>类核心的作用就是对数组和对象做响应式的处理。</p>
<h1 id="15、defineReactive"><a href="#15、defineReactive" class="headerlink" title="15、defineReactive"></a>15、defineReactive</h1><p>这一小节我们来看一下<code>Observer</code>类中的<code>defineReactive</code>方法，在上一小节中我们说过该方法的作用就是：就是将对象中的属性转换成<code>getter</code>和<code>setter</code>.</p>
<p>下面看一下<code>defineReactive</code>方法中的源码实现。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 为一个对象定义一个响应式的属性</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Define a reactive property on an Object.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">//shallow的值为true,表示只监听对象中的第一层属性，如果是false那就是深度监听，也就是说当key这个属性的值是一个对象，那么还需要监听这个对象中的每个值的变化。</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">defineReactive</span>(<span class="params"></span></span><br><span class="line"><span class="params">  obj: <span class="built_in">Object</span>, <span class="comment">//目标对象</span></span></span><br><span class="line"><span class="params">  key: string, <span class="comment">//转换的属性</span></span></span><br><span class="line"><span class="params">  val: any,</span></span><br><span class="line"><span class="params">  customSetter?: ?<span class="built_in">Function</span>, <span class="comment">//用户自定义的函数，很少会用到。</span></span></span><br><span class="line"><span class="params">  shallow?: boolean</span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 创建依赖对象实例，其作用就是为key收集依赖，也就是收集所有观察当前key这个属性的所有的watcher.</span></span><br><span class="line">  <span class="keyword">const</span> dep = <span class="keyword">new</span> <span class="title class_">Dep</span>();</span><br><span class="line">  <span class="comment">// 获取 obj 的属性描述符对象,在属性描述符中可以定义getter/setter. 还可以定义configurable,也就是该属性是否为可配置的。</span></span><br><span class="line">  <span class="keyword">const</span> property = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(obj, key);</span><br><span class="line">  <span class="comment">//判断是否存在属性描述符并且configurable的值为false。如果configurable的值为false,表明是不可配置的，那么就不能通过delete将这个属性进行删除。、</span></span><br><span class="line">  <span class="comment">//也不能通过 Object.defineProperty进行重新的定义。</span></span><br><span class="line">  <span class="comment">//而在接下的操作中我们需要通过 Object.defineProperty对属性重新定义描述符，所以这里判断了configurable属性如果为false,则直接返回。</span></span><br><span class="line">  <span class="keyword">if</span> (property &amp;&amp; property.<span class="property">configurable</span> === <span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 获取属性中的get和set.因为obj这个对象有可能是用户传入的，如果是用户传入的那么就有可能给obj这个对象中的属性设置了get/set.</span></span><br><span class="line">  <span class="comment">//所以这里先将用户设置的get和set存储起来，后面需要对get/set进行重写,为其增加依赖收集与派发更新的功能。</span></span><br><span class="line">  <span class="comment">// cater for pre-defined getter/setters</span></span><br><span class="line">  <span class="keyword">const</span> getter = property &amp;&amp; property.<span class="property">get</span>;</span><br><span class="line">  <span class="keyword">const</span> setter = property &amp;&amp; property.<span class="property">set</span>;</span><br><span class="line">  <span class="comment">//如果传入了两个参数（obj和key）,这里需要获取对应的key这个属性的值。</span></span><br><span class="line">  <span class="keyword">if</span> ((!getter || setter) &amp;&amp; <span class="variable language_">arguments</span>.<span class="property">length</span> === <span class="number">2</span>) &#123;</span><br><span class="line">    val = obj[key];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 判断shallow是否为false.如果当前的shallow是false,那么就不是浅层的监听。那么需要调用observe，也就是val是一个对象，那么需要将该对象中的所有的属性转换成getter/setter，observe方法返回的就是一个Observer对象</span></span><br><span class="line">  <span class="keyword">let</span> childOb = !shallow &amp;&amp; <span class="title function_">observe</span>(val);</span><br><span class="line">  <span class="comment">//下面就是通过Object.defineProperty将对象的属性转换成了get和set</span></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(obj, key, &#123;</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span>, <span class="comment">//可枚举</span></span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>, <span class="comment">//可以配置</span></span><br><span class="line">    <span class="attr">get</span>: <span class="keyword">function</span> <span class="title function_">reactiveGetter</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="comment">// 首先调用了用户传入的getter,如果用户设置了getter，那么首先会通过用户设置的getter获取对象中的属性值。</span></span><br><span class="line">      <span class="comment">//如果没有设置getter,直接返回我们前面获取到的值。</span></span><br><span class="line">      <span class="keyword">const</span> value = getter ? getter.<span class="title function_">call</span>(obj) : val;</span><br><span class="line">      <span class="comment">//下面就是收集依赖（这块内容我们在一下小节中再来讲解，下面再来看一下set）</span></span><br><span class="line">      <span class="comment">// 如果存在当前依赖目标，即 watcher 对象，则建立依赖</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="title class_">Dep</span>.<span class="property">target</span>) &#123;</span><br><span class="line">        dep.<span class="title function_">depend</span>();</span><br><span class="line">        <span class="comment">// 如果子观察目标存在，建立子对象的依赖关系</span></span><br><span class="line">        <span class="keyword">if</span> (childOb) &#123;</span><br><span class="line">          childOb.<span class="property">dep</span>.<span class="title function_">depend</span>();</span><br><span class="line">          <span class="comment">// 如果属性是数组，则特殊处理收集数组对象依赖</span></span><br><span class="line">          <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(value)) &#123;</span><br><span class="line">            <span class="title function_">dependArray</span>(value);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 返回属性值</span></span><br><span class="line">      <span class="keyword">return</span> value;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">set</span>: <span class="keyword">function</span> <span class="title function_">reactiveSetter</span>(<span class="params">newVal</span>) &#123;</span><br><span class="line">      <span class="comment">//如果用户设置了getter,通过用户设置的getter获取对象中的属性值，否则直接返回前面获取到的值。</span></span><br><span class="line">      <span class="keyword">const</span> value = getter ? getter.<span class="title function_">call</span>(obj) : val;</span><br><span class="line">      <span class="comment">// 如果新值等于旧值或者新值旧值为NaN则不执行</span></span><br><span class="line">      <span class="comment">/* eslint-disable no-self-compare */</span></span><br><span class="line">      <span class="keyword">if</span> (newVal === value || (newVal !== newVal &amp;&amp; value !== value)) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">/* eslint-enable no-self-compare */</span></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; customSetter) &#123;</span><br><span class="line">        <span class="title function_">customSetter</span>();</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 如果没有 setter 直接返回</span></span><br><span class="line">      <span class="comment">// #7981: for accessor properties without setter</span></span><br><span class="line">      <span class="keyword">if</span> (getter &amp;&amp; !setter) <span class="keyword">return</span>;</span><br><span class="line">      <span class="comment">// 如果setter存在则调用，为对象中的属性赋值</span></span><br><span class="line">      <span class="keyword">if</span> (setter) &#123;</span><br><span class="line">        setter.<span class="title function_">call</span>(obj, newVal);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//当getter和setter都不存在，将新值赋值给旧值。</span></span><br><span class="line">        val = newVal;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 如果新值是对象，那么把这个对象的属性再次转换成getter/setter</span></span><br><span class="line">      <span class="comment">//childOb就是一个Observe对象。</span></span><br><span class="line">      childOb = !shallow &amp;&amp; <span class="title function_">observe</span>(newVal);</span><br><span class="line">      <span class="comment">// 派发更新(发布更改通知)</span></span><br><span class="line">      dep.<span class="title function_">notify</span>();</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="16、依赖收集"><a href="#16、依赖收集" class="headerlink" title="16、依赖收集"></a>16、依赖收集</h1><p>在<code>defineReactive</code>方法中定义了<code>getter/setter</code>.在<code>getter</code>中做了收集依赖。依赖收集就是把依赖该属性的<code>watcher</code>对象，添加到<code>dep</code>中的<code>subs</code>数组中。</p>
<p>当数据发生变化后，通知数组中的<code>watch</code>,在前面的课程中，我们模拟过这个过程，下面看一下在<code>Vue</code>中是怎样实现的。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//下面就是收集依赖</span></span><br><span class="line"><span class="comment">// 判断Dep中是否有target属性，该属性中存储的就是watcher对象</span></span><br><span class="line"><span class="keyword">if</span> (<span class="title class_">Dep</span>.<span class="property">target</span>) &#123;</span><br><span class="line">  <span class="comment">//depend方法就是进行依赖收集，就是把watch对象添加到Dep中的subs数组中。</span></span><br><span class="line">  dep.<span class="title function_">depend</span>();</span><br><span class="line">  <span class="comment">// 如果子对象存在，建立子对象的依赖关系</span></span><br><span class="line">  <span class="keyword">if</span> (childOb) &#123;</span><br><span class="line">    <span class="comment">//每一个Observer对象，都有一个dep对象，然后调用depend方法建立子对象的依赖关系。</span></span><br><span class="line">    childOb.<span class="property">dep</span>.<span class="title function_">depend</span>();</span><br><span class="line">    <span class="comment">// 如果属性是数组，则收集数组对象依赖</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(value)) &#123;</span><br><span class="line">      <span class="title function_">dependArray</span>(value);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在我们对以上代码有了一个基本的了解，下面我们思考一个问题是,在什么时候给<code>Dep</code>的<code>target</code>属性赋值的？要回答这个问题，我们首先要考虑的是，什么时候创建<code>Watcher</code>对象的。</p>
<p>在<code>src/core/instance/lifecycle.js</code>文件中创建了<code>Watcher</code>对象。</p>
<p>找到该文件中的<code>mountComponent</code>方法，在该方法中创建了<code>Watcher</code>对象。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// we set this to vm._watcher inside the watcher&#x27;s constructor</span></span><br><span class="line"><span class="comment">// since the watcher&#x27;s initial patch may call $forceUpdate (e.g. inside child</span></span><br><span class="line"><span class="comment">// component&#x27;s mounted hook), which relies on vm._watcher being already defined</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Watcher</span>(</span><br><span class="line">  vm,</span><br><span class="line">  updateComponent,</span><br><span class="line">  noop,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="title function_">before</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (vm.<span class="property">_isMounted</span> &amp;&amp; !vm.<span class="property">_isDestroyed</span>) &#123;</span><br><span class="line">        <span class="title function_">callHook</span>(vm, <span class="string">&quot;beforeUpdate&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="literal">true</span> <span class="comment">/* isRenderWatcher */</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>下面进入<code>Watcher</code>的内部，看一下在其内部是怎样给<code>Dep</code>的<code>target</code>属性赋值的。在其内部有个<code>get</code>方法，在该方法的内部的内部调用了<code> pushTarget(this)</code>,在这里<code>this</code>就是<code>Watcher</code>对象，下面进入<code>pushTarget</code>方法的内部。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Dep.target 用来存放目前正在使用的watcher</span></span><br><span class="line"><span class="comment">// 全局唯一，并且一次也只能有一个watcher被使用</span></span><br><span class="line"><span class="comment">// The current target watcher being evaluated.</span></span><br><span class="line"><span class="comment">// This is globally unique because only one watcher</span></span><br><span class="line"><span class="comment">// can be evaluated at a time.</span></span><br><span class="line"><span class="title class_">Dep</span>.<span class="property">target</span> = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">const</span> targetStack = [];</span><br><span class="line"><span class="comment">// 入栈并将当前 watcher 赋值给 Dep.target</span></span><br><span class="line"><span class="comment">// 父子组件嵌套的时候先把父组件对应的 watcher 入栈，</span></span><br><span class="line"><span class="comment">// 再去处理子组件的 watcher，子组件的处理完毕后，再把父组件对应的 watcher 出栈，继续操作</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">pushTarget</span>(<span class="params">target: ?Watcher</span>) &#123;</span><br><span class="line">  <span class="comment">//将Watcher对象存储到栈中。</span></span><br><span class="line">  <span class="comment">//因为在V2.0以后每一个组件对应一个Watcher对象，如果组件之间有嵌套，先处理子组件，所以这时应该先将父组件的	`Watcher`存储起来，这里是存储到栈中了，</span></span><br><span class="line">  <span class="comment">//子组件处理完毕后，把父组件中的Watcher从栈中弹出，继续处理父组件。</span></span><br><span class="line">  targetStack.<span class="title function_">push</span>(target);</span><br><span class="line">  <span class="title class_">Dep</span>.<span class="property">target</span> = target; <span class="comment">//给Dep中的target赋值。</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">popTarget</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 出栈操作</span></span><br><span class="line">  targetStack.<span class="title function_">pop</span>();</span><br><span class="line">  <span class="title class_">Dep</span>.<span class="property">target</span> = targetStack[targetStack.<span class="property">length</span> - <span class="number">1</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面，我们查看一下<code> dep.depend()</code>这个方法内部的代码。我们知道<code>depend</code>方法的作用就是收集依赖。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将观察对象和 watcher 建立依赖</span></span><br><span class="line"> depend () &#123;</span><br><span class="line">   <span class="keyword">if</span> (<span class="title class_">Dep</span>.<span class="property">target</span>) &#123;</span><br><span class="line">     <span class="comment">// 如果 target 存在，把 dep 对象添加到 watcher 的依赖中</span></span><br><span class="line"></span><br><span class="line">     <span class="title class_">Dep</span>.<span class="property">target</span>.<span class="title function_">addDep</span>(<span class="variable language_">this</span>)</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p><code>Dep.target</code>指的就是<code>Watcher</code> ,所以接下来我们查看<code>observer/watcher.js</code>这个文件中的代码。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">addDep (<span class="attr">dep</span>: <span class="title class_">Dep</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> id = dep.<span class="property">id</span><span class="comment">//唯一表示，每次创建一个Dep对象的时候，会让该编号加1，这里可以进入Dep中查看。例如，在页面中有两个&#123;&#123;msg&#125;&#125;，针对这个属性，只会收集一次依赖，即使使用两次msg，这样就避免了重复收集依赖。</span></span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">newDepIds</span>.<span class="title function_">has</span>(id)) &#123;</span><br><span class="line">      <span class="comment">//如果在newDepIds集合中没有id,将其添加到该集合中。</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">newDepIds</span>.<span class="title function_">add</span>(id)</span><br><span class="line">      <span class="comment">//将dep对象添加到newDeps集合中。</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">newDeps</span>.<span class="title function_">push</span>(dep)</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">depIds</span>.<span class="title function_">has</span>(id)) &#123;</span><br><span class="line">        <span class="comment">// 调用dep对象中的addSub方法，将Watcher对象添加到subs数组中。this为Watcher对象。</span></span><br><span class="line">      dep.<span class="title function_">addSub</span>(<span class="variable language_">this</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面我们来看一下<code>Dep</code>中的<code>addSub</code>方法</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将Watcher对象添加到subs数组中。</span></span><br><span class="line">addSub (<span class="attr">sub</span>: <span class="title class_">Watcher</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">subs</span>.<span class="title function_">push</span>(sub)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h1 id="17、数组的响应式处理"><a href="#17、数组的响应式处理" class="headerlink" title="17、数组的响应式处理"></a>17、数组的响应式处理</h1><p>数组的响应式处理的核心代码在<code>Observer</code>类的构造函数中(<code>/core/observer/index.js</code>)</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">constructor</span> (<span class="attr">value</span>: any) &#123;</span><br><span class="line">   <span class="variable language_">this</span>.<span class="property">value</span> = value</span><br><span class="line">   <span class="variable language_">this</span>.<span class="property">dep</span> = <span class="keyword">new</span> <span class="title class_">Dep</span>()</span><br><span class="line">   <span class="comment">// 初始化实例的 vmCount 为0</span></span><br><span class="line">   <span class="variable language_">this</span>.<span class="property">vmCount</span> = <span class="number">0</span></span><br><span class="line">   <span class="comment">// 将实例挂载到观察对象的 __ob__ 属性</span></span><br><span class="line">   <span class="title function_">def</span>(value, <span class="string">&#x27;__ob__&#x27;</span>, <span class="variable language_">this</span>)</span><br><span class="line">   <span class="comment">// 数组的响应式处理</span></span><br><span class="line">   <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(value)) &#123;</span><br><span class="line">     <span class="comment">//  export const hasProto = &#x27;__proto__&#x27; in &#123;&#125;</span></span><br><span class="line">       <span class="comment">//判断当前浏览器是否支持对象的原型这个属性，目的完成浏览器兼容的处理</span></span><br><span class="line">     <span class="keyword">if</span> (hasProto) &#123;</span><br><span class="line">         <span class="comment">//支持对象的原型，则调用如下的函数，</span></span><br><span class="line">         <span class="comment">//value是数组，</span></span><br><span class="line">         <span class="comment">//arrayMethods:数组相关的方法。</span></span><br><span class="line">         <span class="comment">//该方法重新设置数组的原型属性，对应的值为arrayMthods.</span></span><br><span class="line">       <span class="title function_">protoAugment</span>(value, arrayMethods)</span><br><span class="line">     &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">       <span class="title function_">copyAugment</span>(value, arrayMethods, arrayKeys)</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="comment">// 为数组中的每一个对象创建一个 observer 实例</span></span><br><span class="line">     <span class="variable language_">this</span>.<span class="title function_">observeArray</span>(value)</span><br><span class="line">   &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">     <span class="comment">// 遍历对象中的每一个属性，转换成 setter/getter</span></span><br><span class="line">     <span class="variable language_">this</span>.<span class="title function_">walk</span>(value)</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p><code>arrayMethods</code>的实现如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//数组构造函数的原型</span></span><br><span class="line"><span class="keyword">const</span> arrayProto = <span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>;</span><br><span class="line"><span class="comment">//使用Object.create创建一个对象，让对象的原型指向arrayProto，也就是数组的prototype</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> arrayMethods = <span class="title class_">Object</span>.<span class="title function_">create</span>(arrayProto);</span><br><span class="line"><span class="comment">//我们可以看到，如下内容都是数组中的方法，而且这些方法会对数组进行修改，例如push向数组增加内容，造成了原有数组的更新。</span></span><br><span class="line"><span class="comment">//而当数组中的内容发生了变化后，我们要调用Dep中的notity方法发送通知。通知watcher,数据发生了变化，要重新更新视图。</span></span><br><span class="line"><span class="comment">//但是数组的原生方法不知道Dep,也就不会调用Dep中的notity方法。所以说要做一些处理。下面看一下怎样进行处理的。</span></span><br><span class="line"><span class="keyword">const</span> methodsToPatch = [</span><br><span class="line">  <span class="string">&quot;push&quot;</span>,</span><br><span class="line">  <span class="string">&quot;pop&quot;</span>,</span><br><span class="line">  <span class="string">&quot;shift&quot;</span>,</span><br><span class="line">  <span class="string">&quot;unshift&quot;</span>,</span><br><span class="line">  <span class="string">&quot;splice&quot;</span>,</span><br><span class="line">  <span class="string">&quot;sort&quot;</span>,</span><br><span class="line">  <span class="string">&quot;reverse&quot;</span>,</span><br><span class="line">];</span><br><span class="line"><span class="comment">//对methodsToPatch数组进行遍历。</span></span><br><span class="line"><span class="comment">//method表示的是从methodsToPatch数组中取出来的方法的名字</span></span><br><span class="line">methodsToPatch.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">method</span>) &#123;</span><br><span class="line">  <span class="comment">// cache original method</span></span><br><span class="line">  <span class="comment">// arrayProto：数组的原型，这里就是获取数组的原始方法，例如push,pop等</span></span><br><span class="line">  <span class="keyword">const</span> original = arrayProto[method];</span><br><span class="line">  <span class="comment">// 调用 Object.defineProperty() ，将method中存储的方法的名字，重新定义到arrayMthods,也就是给arrayMthods对象，重新定义push,pop等这些方法。</span></span><br><span class="line">  <span class="comment">// 方法的值，就是defineProperty()方法的第三个参数mutator，该方法需要参数args:该参数中存储的就是我们在调用push或者是pop时传递的内容。</span></span><br><span class="line">  <span class="title function_">def</span>(arrayMethods, method, <span class="keyword">function</span> <span class="title function_">mutator</span>(<span class="params">...args</span>) &#123;</span><br><span class="line">    <span class="comment">// 执行数组的原始方法</span></span><br><span class="line">    <span class="keyword">const</span> result = original.<span class="title function_">apply</span>(<span class="variable language_">this</span>, args);</span><br><span class="line">    <span class="comment">// 获取数组关联的Observer对象</span></span><br><span class="line">    <span class="keyword">const</span> ob = <span class="variable language_">this</span>.<span class="property">__ob__</span>;</span><br><span class="line">    <span class="comment">//存储数组中新增的内容，例如如果是push,unshift，则将args赋值给inserted，因为这时args存储的就是新增的内容。</span></span><br><span class="line">    <span class="keyword">let</span> inserted;</span><br><span class="line">    <span class="keyword">switch</span> (method) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;push&quot;</span>:</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;unshift&quot;</span>:</span><br><span class="line">        inserted = args;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;splice&quot;</span>:</span><br><span class="line">        <span class="comment">//如果是splice方法，那么把第三个值存储到inserted中。</span></span><br><span class="line">        inserted = args.<span class="title function_">slice</span>(<span class="number">2</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果有新增的元素，将会重新遍历数组中的元素，并且将其设置为响应式数据。也就是说，调用push,unshift,splice方法，向数组中添加的内容都是响应式的。</span></span><br><span class="line">    <span class="keyword">if</span> (inserted) ob.<span class="title function_">observeArray</span>(inserted);</span><br><span class="line">    <span class="comment">// notify change</span></span><br><span class="line">    <span class="comment">// 找到Observer中的dep对象，调用其中的notify方法来发送通知</span></span><br><span class="line">    ob.<span class="property">dep</span>.<span class="title function_">notify</span>();</span><br><span class="line">    <span class="comment">//返回方法执行的结果。</span></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>以上就是对数组中的会修改数组原有内容的方法的处理。具体的过程就是先调用数组中的原始方法，例如<code>push</code>,<code>pop</code>等这些方法。</p>
<p>下面就是找到对数组进行新增元素的这些方法，例如:<code>push</code>,<code>unshift</code>,<code>splice，</code>,如果新增了元素，就调用<code>observer</code>中的<code>observerArray</code>这个方法，去遍历数组中的这些新增的元素，然后转换成响应式。当调用了数组中的新增元素的这些方法后，会发送通知。最后返回方法执行的结果。</p>
<p>下面我们再回到<code>Observer</code>类的构造函数中(<code>/core/observer/index.js</code>)。</p>
<p>如果浏览器不支持原型属性会调用<code>copyAugment</code>方法，该方法有三个参数，前两个参数与<code>protoAugment</code>方法参数的含义是一样的，第三个参数是<code>arrayKeys</code>.</p>
<p><code>arrayKeys</code>具体的含义是：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arrayKeys = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(arrayMethods);</span><br></pre></td></tr></table></figure>

<p>获取数组中的<code>push</code>,<code>pop</code>等方法的名字，<code>arrayKeys</code>是一个数组。</p>
<p>下面看一下<code>copyAugment</code>方法中的代码</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">copyAugment</span>(<span class="params">target: <span class="built_in">Object</span>, src: <span class="built_in">Object</span>, keys: <span class="built_in">Array</span>&lt;string&gt;</span>) &#123;</span><br><span class="line">  <span class="comment">//变量传递过来的数组中方法的名字</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = keys.<span class="property">length</span>; i &lt; l; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> key = keys[i];</span><br><span class="line">    <span class="comment">//给数组对象重新定义这些数组的方法，例如pop,push. 当然这些方法都是经过处理的。该方法的作用与protoAugment方法的一样</span></span><br><span class="line">    <span class="title function_">def</span>(target, key, src[key]);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面我们再来看一下<code>observeArray</code>方法。</p>
<p>遍历数组中的成员，为数组中的每个对象设置为响应式的对象。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">  observeArray (<span class="attr">items</span>: <span class="title class_">Array</span>&lt;any&gt;) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = items.<span class="property">length</span>; i &lt; l; i++) &#123;</span><br><span class="line">      <span class="title function_">observe</span>(items[i])</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="18、数组练习"><a href="#18、数组练习" class="headerlink" title="18、数组练习"></a>18、数组练习</h1><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span>&#123;&#123;arr&#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;vue.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> vm = <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&quot;#app&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">arr</span>: [<span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>],</span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript">  <span class="comment">//看一下如下操作是否为响应式</span></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// vm.arr.push(8)</span></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// vm.arr[0]=100</span></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// vm.arr.length=0</span></span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>通过前面的代码的阅读，我们知道<code>push</code>方法，在<code>Vue</code>中做了一定的处理，当通过<code>push</code>方法向数组中添加了一个新的数据后，对应的视图页面也会进行更新。</p>
<p><code>vm.arr[0]=100</code>,会修改数组中的内容，但是当数组中的内容修改了以后，视图并没有发生任何的变化，所以这种操作并不是响应式的。也就是说通过数组的索引来</p>
<p>修改数组的时候，并没有调用<code>Dep</code>中的<code>notify</code>,也就没有通知<code>watcher</code>去重新渲染视图。通过源码，我们并没有发现对这种情况的处理，也就是没有监听数组中的每个属性（<code>index</code>,<code>length</code>都是数组的属性）将其转换成响应式，同理<code>vm.arr.length=0</code>也不是响应式的。</p>
<p>在源码中，我们看的是对数组进行遍历，对数组中的每个元素中是对象的元素转换成了响应式。</p>
<p>如果现在我们想让<code> vm.arr[0]=100</code>和<code> vm.arr.length=0</code>这种操作变成响应式的效果，应该怎样实现呢？</p>
<p>首先，我们先来看<code> vm.arr[0]=100</code>,就是修改数组中的第一个元素，这里我们可以使用<code>splice</code>方法来达到相同的目的，而且通过前面阅读源码我们知道，<code>splice</code>方法是响应式的，也就是通过该发你规范修改完了数组后，对应的视图也会进行。所以对应的代码为：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//第一个参数0，表示删除arr数组中的第一个元素。</span></span><br><span class="line"><span class="comment">//第二个参数1.表示的是删除的个数</span></span><br><span class="line"><span class="comment">//第三个参数100，表示的是把删除的元素用100来替换。</span></span><br><span class="line">vm.<span class="property">arr</span>.<span class="title function_">splice</span>(<span class="number">0</span>, <span class="number">1</span>, <span class="number">100</span>);</span><br></pre></td></tr></table></figure>

<p>下面我们再来看一下关于<code>vm.arr.length=0</code>,表示的是清空数组中的内容。</p>
<p>为了达到响应式的效果，这里也可以使用 splice 方法来完成，清空数组的操作。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vm.<span class="property">arr</span>.<span class="title function_">splice</span>(<span class="number">0</span>); <span class="comment">//清空数组中的所有元素</span></span><br></pre></td></tr></table></figure>

<p>通过查看源码我们对上面的问题有了更加深入的理解。</p>
<h1 id="19、Watcher"><a href="#19、Watcher" class="headerlink" title="19、Watcher"></a>19、Watcher</h1><p>关于<code>Watcher</code>我们首先会查看一下在首次渲染的时候的执行过程，然后再来看一下当数据发生变化后，<code>Watcher</code>的执行过程。</p>
<p><code>Watcher</code>分为三种，<code>Computed Watcher</code>(计算属性，本质也是通过<code>Watcher</code>来实现的),用户<code>Watcher</code>(侦听器)，渲染<code>Watcher</code></p>
<p>前面两种<code>Watcher</code>是在<code>initState</code>的时候初始化的。</p>
<p>下面我们来复习一下首次渲染的时候<code>Watcher</code>的执行过程。</p>
<p><code>/src/core/instance/lifecycle.js</code>中的<code>mountComponent</code>组件中创建的。</p>
<p>如下代码所示：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">//vm:Vue的实例</span></span><br><span class="line"><span class="comment">// updateComponent</span></span><br><span class="line"><span class="comment">//noop空函数，</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Watcher</span>(</span><br><span class="line">  vm,</span><br><span class="line">  updateComponent,</span><br><span class="line">  noop,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="title function_">before</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (vm.<span class="property">_isMounted</span> &amp;&amp; !vm.<span class="property">_isDestroyed</span>) &#123;</span><br><span class="line">        <span class="comment">//触发beforeUpate方法。</span></span><br><span class="line">        <span class="title function_">callHook</span>(vm, <span class="string">&quot;beforeUpdate&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="literal">true</span> <span class="comment">/* isRenderWatcher */</span></span><br><span class="line">); <span class="comment">//ture表示渲染的watcher.</span></span><br></pre></td></tr></table></figure>

<p>下面进入<code>Watcher</code>类中，看一下做了哪些事情。以下的代码是<code>Watcher</code>类的构造函数。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">constructor</span> (</span><br><span class="line">  <span class="attr">vm</span>: <span class="title class_">Component</span>,</span><br><span class="line">  <span class="attr">expOrFn</span>: string | <span class="title class_">Function</span>,</span><br><span class="line">  <span class="attr">cb</span>: <span class="title class_">Function</span>,</span><br><span class="line">  options?: ?<span class="title class_">Object</span>,</span><br><span class="line">  isRenderWatcher?: boolean</span><br><span class="line">) &#123;</span><br><span class="line">    <span class="comment">//将Vue的实例记录到了vm这个属性中。</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">vm</span> = vm</span><br><span class="line">  <span class="keyword">if</span> (isRenderWatcher) &#123;</span><br><span class="line">      <span class="comment">//如果是渲染`Watcher`,则将`Watcher`的实例保存到`Vue`实例中的`_watcher`属性中。</span></span><br><span class="line">    vm.<span class="property">_watcher</span> = <span class="variable language_">this</span></span><br><span class="line">  &#125;</span><br><span class="line">    <span class="comment">//将所有的`Watcher`实例都保存到`Vue`实例中的`_watchers`这个数组中。</span></span><br><span class="line">    <span class="comment">// _watchers数组中不仅存储了渲染`Watcher`,还存储了计算属性对应的watcher,还有就是侦听器。</span></span><br><span class="line">  vm.<span class="property">_watchers</span>.<span class="title function_">push</span>(<span class="variable language_">this</span>)</span><br><span class="line">  <span class="comment">// options</span></span><br><span class="line">  <span class="keyword">if</span> (options) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">deep</span> = !!options.<span class="property">deep</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">user</span> = !!options.<span class="property">user</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lazy</span> = !!options.<span class="property">lazy</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">sync</span> = !!options.<span class="property">sync</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">before</span> = options.<span class="property">before</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">deep</span> = <span class="variable language_">this</span>.<span class="property">user</span> = <span class="variable language_">this</span>.<span class="property">lazy</span> = <span class="variable language_">this</span>.<span class="property">sync</span> = <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">    <span class="comment">//创建渲染watcher的实例的时候，传递过来的cb函数就是一个noop空函数。</span></span><br><span class="line">    <span class="comment">//如果是用户创建的`Watcher`的时候，传递过来的就是一个回调函数。</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">cb</span> = cb</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">id</span> = ++uid <span class="comment">//为了区分每个watcher,创建一个编号 uid for batching</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">active</span> = <span class="literal">true</span><span class="comment">// active表示这个watcher是否为活动的watcher,如果为true,则为活动的watcher.</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">dirty</span> = <span class="variable language_">this</span>.<span class="property">lazy</span> <span class="comment">// for lazy watchers</span></span><br><span class="line">    <span class="comment">//下面的集合记录的都是Dep</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">deps</span> = []</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">newDeps</span> = []</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">depIds</span> = <span class="keyword">new</span> <span class="title class_">Set</span>()</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">newDepIds</span> = <span class="keyword">new</span> <span class="title class_">Set</span>()</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">expression</span> = process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span></span><br><span class="line">    ? expOrFn.<span class="title function_">toString</span>()</span><br><span class="line">    : <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="comment">// parse expression for getter</span></span><br><span class="line">    <span class="comment">//判断expOrFn是否为函数，我们在前面创建渲染watcher的时候，传递过来的updateComponent函数给了expOrFn这个参数。</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> expOrFn === <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">      <span class="comment">//将函数保存到了getter中。</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">getter</span> = expOrFn</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// expOrFn 是字符串的时候，也就是创建侦听器的时候传递的内容，例如 watch: &#123; &#x27;person.name&#x27;: function... &#125;</span></span><br><span class="line">      <span class="comment">// 这时候侦听器侦听的内容是字符串，也就是person.name</span></span><br><span class="line">    <span class="comment">// parsePath(&#x27;person.name&#x27;) 返回一个函数获取 person.name 的值</span></span><br><span class="line">      <span class="comment">//parsePath的作用就是生成一个函数，来获取`person.name`的值。将返回的函数记录到了getter中。记录geeter中的目的，就是当获取属性值的时候会触发对应的getter,当触发getter的时候会触发依赖。</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">getter</span> = <span class="title function_">parsePath</span>(expOrFn)</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">getter</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">getter</span> = noop</span><br><span class="line">      process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; <span class="title function_">warn</span>(</span><br><span class="line">        <span class="string">`Failed watching path: &quot;<span class="subst">$&#123;expOrFn&#125;</span>&quot; `</span> +</span><br><span class="line">        <span class="string">&#x27;Watcher only accepts simple dot-delimited paths. &#x27;</span> +</span><br><span class="line">        <span class="string">&#x27;For full control, use a function instead.&#x27;</span>,</span><br><span class="line">        vm</span><br><span class="line">      )</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">    <span class="comment">//如果是计算属性，lazy的值为true,表示延迟执行。如果是渲染watcher,会立即调用get方法。</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">value</span> = <span class="variable language_">this</span>.<span class="property">lazy</span></span><br><span class="line">    ? <span class="literal">undefined</span></span><br><span class="line">    : <span class="variable language_">this</span>.<span class="title function_">get</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>下面就是<code>get</code>方法的源码。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">get () &#123;</span><br><span class="line">    <span class="comment">//把当前的watcher对象入栈，并且把当前的watcher赋值给Dep的target属性。</span></span><br><span class="line">    <span class="comment">//当有父子组件嵌套的时候，先将父组件的watcher入栈，然后对子组件进行处理，处理完毕后，在从栈中获取父组件的watcher进行处理。</span></span><br><span class="line">  <span class="title function_">pushTarget</span>(<span class="variable language_">this</span>)</span><br><span class="line">  <span class="keyword">let</span> value</span><br><span class="line">  <span class="keyword">const</span> vm = <span class="variable language_">this</span>.<span class="property">vm</span></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="comment">//对getter函数进行调用，如果是渲染函数，这里调用的是updateComponent</span></span><br><span class="line">      <span class="comment">//当updateComponent函数执行完毕后会将虚拟DOM转换成真实的DOM,然后渲染到页面中。</span></span><br><span class="line">    value = <span class="variable language_">this</span>.<span class="property">getter</span>.<span class="title function_">call</span>(vm, vm)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">user</span>) &#123;</span><br><span class="line">      <span class="title function_">handleError</span>(e, vm, <span class="string">`getter for watcher &quot;<span class="subst">$&#123;<span class="variable language_">this</span>.expression&#125;</span>&quot;`</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> e</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="comment">// &quot;touch&quot; every property so they are all tracked as</span></span><br><span class="line">    <span class="comment">// dependencies for deep watching</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">deep</span>) &#123;</span><br><span class="line">        <span class="comment">//表示进行深度监听，深度监听表示的就是如果监听的是一个对象的话，会监听这个对象下的子属性、</span></span><br><span class="line">      <span class="title function_">traverse</span>(value)</span><br><span class="line">    &#125;</span><br><span class="line">      <span class="comment">//处理完毕后，做一些清理的工作，例如将watcher从栈中弹出</span></span><br><span class="line">    <span class="title function_">popTarget</span>()</span><br><span class="line">      <span class="comment">//将Watcher从subs数组中移除</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">cleanupDeps</span>()</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> value</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>以上就是首次渲染的时候，<code>Watcher</code>的执行过程。</p>
<p><strong>下面，我们来看一下当数据发生更新的时候，<code>Watcher</code>是怎样工作的？</strong></p>
<p>下面我们来查看一下<code>observer/dep.js</code></p>
<p>我们知道当数据发生了变化后，会调用<code>Dep</code>中的<code>notify</code>这个方法，下面我们来看一下该方法的代码，如下所示:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">  <span class="comment">// 发布通知</span></span><br><span class="line">  notify () &#123;</span><br><span class="line">    <span class="comment">// stabilize the subscriber list first</span></span><br><span class="line">      <span class="comment">//subs数组中存储的就是`watcher`对象，调用slice方法实现了克隆，因为下面会对subs数组中的内容进行排序。</span></span><br><span class="line">    <span class="keyword">const</span> subs = <span class="variable language_">this</span>.<span class="property">subs</span>.<span class="title function_">slice</span>()</span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; !config.<span class="property">async</span>) &#123;</span><br><span class="line">      <span class="comment">// subs aren&#x27;t sorted in scheduler if not running async</span></span><br><span class="line">      <span class="comment">// we need to sort them now to make sure they fire in correct</span></span><br><span class="line">      <span class="comment">// order</span></span><br><span class="line">        <span class="comment">//按照Watcher对象中的id值进行从小到大的排序，也就是按照`watcher`的创建顺序进行排序，从而保证了在执行`watcher`的时候顺序是正确的。</span></span><br><span class="line">      subs.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">id</span> - b.<span class="property">id</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 调用每个watcher对象的update方法实现更新</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = subs.<span class="property">length</span>; i &lt; l; i++) &#123;</span><br><span class="line">      subs[i].<span class="title function_">update</span>()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的代码中，对<code>subs</code>数组进行遍历，然后获取对应的<code>Watcher</code>,然后调用<code>Watcher</code>对象的<code>update</code>方法，下面我们来看一下<code>update</code>这个方法中的内容</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">update () &#123;</span><br><span class="line">    <span class="comment">/* istanbul ignore else */</span></span><br><span class="line">    <span class="comment">//在渲染watcher的时候，把lazy属性与sync属性设置为了false</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">lazy</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">dirty</span> = <span class="literal">true</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">sync</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">run</span>()</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//渲染watcher会执行queueWatcher,</span></span><br><span class="line">        <span class="comment">//该方法的作用就是将watcher的实例放到一个队列中。</span></span><br><span class="line">      <span class="title function_">queueWatcher</span>(<span class="variable language_">this</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>下面，我们来查看一下<code>queueWatcher</code>方法内部的代码：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">queueWatcher</span>(<span class="params">watcher: Watcher</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> id = watcher.<span class="property">id</span>; <span class="comment">//获取watcher的id属性</span></span><br><span class="line">  <span class="comment">//has是一个对象，下面获取has中的值，如果为null,表示当前这个watcher对象还没有被处理。</span></span><br><span class="line">  <span class="comment">//下面加这个判断的目的，就是为了防止watcher被重复性的处理。</span></span><br><span class="line">  <span class="keyword">if</span> (has[id] == <span class="literal">null</span>) &#123;</span><br><span class="line">    has[id] = <span class="literal">true</span>; <span class="comment">//把has[id]设置为true,表明当前的watcher对象已经被处理了。</span></span><br><span class="line">    <span class="comment">//下面就是开始正式的处理watcher</span></span><br><span class="line">    <span class="comment">//flushing为true,表明queue这个队列正在被处理。队列中存储的是watcher对象，也就是watcher对象正在被处理。</span></span><br><span class="line">    <span class="comment">//如果下面的判断条件成立，表明没有处理队列，那么就将watcher放到队列中。</span></span><br><span class="line">    <span class="keyword">if</span> (!flushing) &#123;</span><br><span class="line">      queue.<span class="title function_">push</span>(watcher);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// if already flushing, splice the watcher based on its id</span></span><br><span class="line">      <span class="comment">// if already past its id, it will be run next immediately.</span></span><br><span class="line">      <span class="comment">//如果执行else表明队列正在被处理，那么这里需要找到队列中一个合适位置，然后把watcher插入到队列中。</span></span><br><span class="line">      <span class="comment">//那么这里是怎样获取位置的呢？</span></span><br><span class="line">      <span class="comment">//首先获取队列的长度。</span></span><br><span class="line">      <span class="comment">//index表示现在处理到了队列中的第几个元素，如果i大于index,则表明当前这个队列并没有处理完。</span></span><br><span class="line">      <span class="comment">//下面需要从后往前，取到队列中的每个watcher对象，然后判断id是否大于watcher.id,如果大于正在处理的这个watcher的id,那么这个位置就是插入watcher的位置</span></span><br><span class="line">      <span class="keyword">let</span> i = queue.<span class="property">length</span> - <span class="number">1</span>;</span><br><span class="line">      <span class="keyword">while</span> (i &gt; index &amp;&amp; queue[i].<span class="property">id</span> &gt; watcher.<span class="property">id</span>) &#123;</span><br><span class="line">        i--;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//下面就是把待处理的watcher放到队列的合适位置。</span></span><br><span class="line">      queue.<span class="title function_">splice</span>(i + <span class="number">1</span>, <span class="number">0</span>, watcher);</span><br><span class="line"></span><br><span class="line">      <span class="comment">//上面的代码其实就是把当前将要处理的watcher对象放到队列中。</span></span><br><span class="line">      <span class="comment">//下面就开始执行队列中的watcher对象。</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// queue the flush</span></span><br><span class="line">    <span class="comment">//下面判断的含义就是判断一下当前的队列是否正在被执行。</span></span><br><span class="line">    <span class="comment">//如果watiing为false,表明当前队列没有被执行，下面需要将waiting设置为true.</span></span><br><span class="line">    <span class="keyword">if</span> (!waiting) &#123;</span><br><span class="line">      waiting = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&quot;production&quot;</span> &amp;&amp; !config.<span class="property">async</span>) &#123;</span><br><span class="line">        <span class="comment">//开发环境直接调用下面的flushSchedulerQueue方法</span></span><br><span class="line">        <span class="comment">//flushSchedulerQueue方法的作用会遍历队列，然后调用队列中每个watcher的run方法。</span></span><br><span class="line">        <span class="title function_">flushSchedulerQueue</span>();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//生产环境会将flushSchedulerQueue函数传递到nextTick函数中，后面再来讲解nextTick的应用。</span></span><br><span class="line">      <span class="title function_">nextTick</span>(flushSchedulerQueue);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面我们来看一下<code>flushSchedulerQueue</code>方法内部的代码。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">flushSchedulerQueue</span> () &#123;</span><br><span class="line">  currentFlushTimestamp = <span class="title function_">getNow</span>()</span><br><span class="line">  flushing = <span class="literal">true</span><span class="comment">//将flushing设置为true,表明正在处理队列</span></span><br><span class="line">  <span class="keyword">let</span> watcher, id</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Sort queue before flush.</span></span><br><span class="line">  <span class="comment">// This ensures that:</span></span><br><span class="line">  <span class="comment">//组件的更新顺序是从父组件到子组件，因为先创建了父组件后创建了子组件</span></span><br><span class="line">  <span class="comment">// 1. Components are updated from parent to child. (because parent is always</span></span><br><span class="line">  <span class="comment">//    created before the child)</span></span><br><span class="line">  <span class="comment">//组件的用户watcher,要在渲染watcher之前运行，因为用户watcher是在渲染watcehr之前创建的。</span></span><br><span class="line">  <span class="comment">// 2. A component&#x27;s user watchers are run before its render watcher (because</span></span><br><span class="line">  <span class="comment">//    user watchers are created before the render watcher)</span></span><br><span class="line">  <span class="comment">// 如果一个组件，在父组件执行前被销毁了，那么对应的watcher应该跳过。</span></span><br><span class="line">  <span class="comment">// 3. If a component is destroyed during a parent component&#x27;s watcher run,</span></span><br><span class="line">  <span class="comment">//    its watchers can be skipped.</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">//对队列中的watcher进行排序，排序的方式是根据对应id，从小到大的顺序 进行排序。也就是按照watcher的创建顺序进行排列。</span></span><br><span class="line">  <span class="comment">//为什么要进行排序呢？上面的注释已经给出了三点的说明</span></span><br><span class="line">  queue.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">id</span> - b.<span class="property">id</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// do not cache length because more watchers might be pushed</span></span><br><span class="line">  <span class="comment">// as we run existing watchers</span></span><br><span class="line">    <span class="comment">//以上注释的含义：不要缓存length,因为watcher在执行的过程中，还会向队列中放入新的watcher.</span></span><br><span class="line">  <span class="keyword">for</span> (index = <span class="number">0</span>; index &lt; queue.<span class="property">length</span>; index++) &#123;</span><br><span class="line">      <span class="comment">//对队列进行遍历，然后取出当前要处理的watcher.</span></span><br><span class="line">    watcher = queue[index]</span><br><span class="line">    <span class="keyword">if</span> (watcher.<span class="property">before</span>) &#123;</span><br><span class="line">        <span class="comment">//判断是否有before这个函数，该函数是在渲染watcher中具有的一个函数。其作用就是触发beforeupdate这个钩子函数。</span></span><br><span class="line">        <span class="comment">//也就是说走到这个位置beforeupate这个钩子函数被触发了。</span></span><br><span class="line">      watcher.<span class="title function_">before</span>()</span><br><span class="line">    &#125;</span><br><span class="line">    id = watcher.<span class="property">id</span><span class="comment">//获取watcher的id</span></span><br><span class="line">    has[id] = <span class="literal">null</span><span class="comment">//将has[id]的值设为null,表明当前的watcher已经被处理过了。</span></span><br><span class="line">    watcher.<span class="title function_">run</span>()<span class="comment">//执行watcher中的run方法。下面看一下run方法中的源码。</span></span><br><span class="line">    <span class="comment">// in dev build, check and stop circular updates.</span></span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; has[id] != <span class="literal">null</span>) &#123;</span><br><span class="line">      circular[id] = (circular[id] || <span class="number">0</span>) + <span class="number">1</span></span><br><span class="line">      <span class="keyword">if</span> (circular[id] &gt; <span class="variable constant_">MAX_UPDATE_COUNT</span>) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">&#x27;You may have an infinite update loop &#x27;</span> + (</span><br><span class="line">            watcher.<span class="property">user</span></span><br><span class="line">              ? <span class="string">`in watcher with expression &quot;<span class="subst">$&#123;watcher.expression&#125;</span>&quot;`</span></span><br><span class="line">              : <span class="string">`in a component render function.`</span></span><br><span class="line">          ),</span><br><span class="line">          watcher.<span class="property">vm</span></span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>下面是<code>run</code>方法的实现代码：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">run () &#123;</span><br><span class="line">    <span class="comment">//标记当前的watcher对象是否为存活的状态。active默认值为true,表明可以对watcher进行处理。</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">active</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> value = <span class="variable language_">this</span>.<span class="title function_">get</span>()<span class="comment">//调用watcher对象中的get方法，在get方法中会进行判断，如果是渲染watcher会调用updatecomponent方法，来渲染组件，更新视图</span></span><br><span class="line">      <span class="comment">//对于渲染watcher来说，对应的updateComponent方法是没有返回值，所以常量value的值为undefined.所以下面的代码不在执行，但是是用户 watcher,那么会调用其对应的回调函数，我们创建侦听器的时候，指定了回调函数。</span></span><br><span class="line">      <span class="keyword">if</span> (</span><br><span class="line">        value !== <span class="variable language_">this</span>.<span class="property">value</span> ||</span><br><span class="line">        <span class="comment">// Deep watchers and watchers on Object/Arrays should fire even</span></span><br><span class="line">        <span class="comment">// when the value is the same, because the value may</span></span><br><span class="line">        <span class="comment">// have mutated.</span></span><br><span class="line">        <span class="title function_">isObject</span>(value) ||</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">deep</span></span><br><span class="line">      ) &#123;</span><br><span class="line">        <span class="comment">// set new value</span></span><br><span class="line">        <span class="keyword">const</span> oldValue = <span class="variable language_">this</span>.<span class="property">value</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">value</span> = value</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">user</span>) &#123;</span><br><span class="line">          <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">cb</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>.<span class="property">vm</span>, value, oldValue)</span><br><span class="line">          &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">            <span class="title function_">handleError</span>(e, <span class="variable language_">this</span>.<span class="property">vm</span>, <span class="string">`callback for watcher &quot;<span class="subst">$&#123;<span class="variable language_">this</span>.expression&#125;</span>&quot;`</span>)</span><br><span class="line">          &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="variable language_">this</span>.<span class="property">cb</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>.<span class="property">vm</span>, value, oldValue)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>以上就是数据更新后，<code>watcher</code>的执行过程。</p>
<p>我们总结一下：当数据发生了变化后，会调用<code>Dep</code>中的<code>notify</code>方法去通知 watcher,首先会将<code>watcher</code>放入到一个队列中，然后遍历队列，调用<code>Watcher</code>对象的<code>run</code>方法，在<code>run</code>方法中调用了渲染<code>watcher</code>的<code>updatecomponet</code>这个函数来渲染组件，更新视图，以上就是整个的处理过程。</p>
<h1 id="20、总结响应式处理的过程"><a href="#20、总结响应式处理的过程" class="headerlink" title="20、总结响应式处理的过程"></a>20、总结响应式处理的过程</h1><p>响应式是从<code>Vue</code>实例的<code>initState</code>方法开始的，在<code>initState</code>中完成了<code>Vue</code>实例状态的初始化，在<code>initState</code>方法的内部调用了<code>initData</code>方法，该方法的作用就是把<code>data</code>属性注入到了<code>Vue</code>的实例中，并且在其内部调用了<code>observe</code>方法，在<code>observe</code>方法中把<code>data</code>对象转换成响应式对象。所以说<code>observe</code>就是响应式的入口，下面我们来看一下在<code>observe</code>方法中做了哪些事情：</p>
<p><code>observe</code>方法所在的位置<code>src/core/observer/index.js</code>.</p>
<p>在调用<code>observe</code>方法的时候，会传递一个参数<code>value</code>,所以在<code>observe</code>方法中首先会判断一下传递过来的参数<code>value</code>是否为对象，如果不是对象直接返回。</p>
<p>然后判断<code>value</code>对象是否有<code>__ob__</code>属性,如果有说明之前已经对其做过响应式的处理，所以直接返回。</p>
<p>如果没有<code>__ob__</code>属性，在创建<code>observer</code>对象，最后将<code>observer</code>对象返回。</p>
<p>那在创建<code>observer</code>对象的时候又做了哪些事情呢？</p>
<p><code>observer</code>对象是有<code>Observer</code>类创建的(位置:<code>src/core/observer/index.js</code>),在<code>Observer</code>类的构造函数中，给<code>value</code>对象定义不可枚举的<code>__ob__</code>属性，并且通过该数据记录当前的<code>observer</code>对象。接下来进行了数组的响应式处理与对象的响应式处理。</p>
<p>在对数组进行响应式处理的时候，主要是设置了数组常用的方法，例如<code>push</code>,<code>pop</code>等。这些方法会改变原数组，所以当这些方法调用的时候，会发送通知。</p>
<p>在发送通知的时候，找到数组对应的<code>__ob__</code>属性,也就是<code>observer</code>对象，再找到<code>observer</code>对象的<code>dep</code>,然后调用<code>dep</code>中的<code>notify</code>方法。</p>
<p>更改了这些数组的方法后，下面就开始遍历数组中的每个成员，对每一个成员再去调用<code>observer</code>,如果这个成员是对象，也会将这个对象转换成响应式。</p>
<p>这就是关于数组的响应式处理。</p>
<p>下面我们再来看一下关于对象的响应式处理。关于对象的响应式处理，调用的是<code>walk</code>方法。</p>
<p>在<code>walk</code>方法内部会遍历对象中的所有属性，对每一个属性调用<code>defineReactive</code>方法（位置：<code>src/core/observer/index.js</code>），在<code>defineReactive</code>方法的内部会为每一个属性创建<code>dep</code>对象。让<code>dep</code>收集依赖。如果当前对象的属性值是对象，则会调用<code>observe</code>,也就是说如果当前对象的属性是对象，调用<code>observe</code>方法的目录就是把这个对象也转换成响应式对象。</p>
<p>在<code>defineReactive</code>方法的内部定义了<code>getter</code>和<code>setter</code>.</p>
<p>在<code>getter</code>中收集依赖，当然在收集依赖的时候，会为每一个属性收集依赖。如果属性的值为对象，也要收集依赖。<code>getter</code>方法最终会返回属性的值。</p>
<p>下面看一下<code>setter</code>,在<code>setter</code>方法中会保存新值，如果新值是对象也会调用<code>observe</code>,把这个新设置的对象也转换为响应式的对象。在<code>setter</code>方法椎间盘每个。数据发生变化，所以会派发通知，调用<code>dep.notify</code>方法。</p>
<p>下面我们再来看一下关于<code>收集</code>依赖的过程。在收集依赖的过程中，首先会调用<code>watcher</code>对象的<code>get</code>方法，在<code>get</code>方法中调用了<code>pushTarget</code>,在该方法中会将当前的<code>watcher</code>对象记录到<code>Dep.target</code>属性中。</p>
<p>在访问<code>data</code>中的成员的时候收集依赖，当访问属性的值时候会触发<code>defineReactive</code>中的<code>getter</code>方法来收集依赖。这时候会把属性对应的<code>watcher</code>对象添加到<code>dep</code>的<code>subs</code>数组中。如果属性的值也是对象，这时会创建一个<code>childOb</code>对象，为子对象收集依赖，目的就是在子对添加或者是删除成员的时候发送通知。</p>
<p>下面我们再来看一下<code>Watcher</code>,当数据发生变化的时候会调用<code>dep.notify</code>方法发送通知，同时在内容调用了<code>update</code>方法，在该方法中又调用了<code>queueWatcher</code>方法，在<code>queueWatcher</code>方法中会判断当前的<code>watcher</code>是否被处理了。如果没有处理，在添加到<code>queue</code>队列中，并调用了<code>flushSchedulerQueue()</code>方法，在该方法中触发了<code>beforeUpdate</code>这个钩子函数，然后调用了<code>watcher.run</code>方法，在该方法中最终调用了<code>updateComponent</code>方法（当前是渲染<code>watcher</code>）.这时已经将数据更新到了视图中，那么我们在页面中看到了最新的数据。最后触发了<code>actived</code>钩子函数和<code>updated</code>钩子函数。</p>
<h1 id="21、动态添加一个响应式属性"><a href="#21、动态添加一个响应式属性" class="headerlink" title="21、动态添加一个响应式属性"></a>21、动态添加一个响应式属性</h1><p>在这里我们考虑一个问题，就是给一个响应式对象，动态增加一个属性，那么这个属性是否为响应式的呢？</p>
<p>下面我们通过如下代码来演示。</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">    &#123;&#123;obj.title&#125;&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">hr</span> /&gt;</span></span><br><span class="line">    &#123;&#123;obj.name&#125;&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">hr</span> /&gt;</span></span><br><span class="line">    &#123;&#123;arr&#125;&#125;</span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./vue.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">srcipt</span>&gt;</span></span><br><span class="line">    const vm=new Vue(&#123; el:&#x27;#app&#x27;, data:&#123; obj:&#123; title:&#x27;Hello World&#x27; &#125;,</span><br><span class="line">    arr:[2,2,3] &#125; &#125;)</span><br><span class="line">  <span class="tag">&lt;/<span class="name">srcipt</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>在上面的代码中，<code>data</code>中定义了一个对象<code>obj</code>,并且<code>obj</code>中有一个属性<code>title</code>,并且展示在了视图中，</p>
<p>同时在视图中还展示了<code>obj</code>对象中的<code>name</code>属性，但是我们并没有在<code>obj</code>对象中创建<code>name</code>属性。下面我们会动态的向</p>
<p><code>obj</code>对象中动态的增加一个属性<code>name</code>,看一下是否会渲染视图。</p>
<p>下面我们在浏览器中打开上面的页面，会展示<code>title</code>与<code>arr</code>数组中的内容。</p>
<p>但是由于没有<code>name</code>，所以不会展示。</p>
<p>打开浏览器的控制台，在<code>Console</code>中输入如下代码：</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vm.obj.name=&quot;abc&quot;</span><br></pre></td></tr></table></figure>

<p>执行完上面的代码后，并没有看到对应的视图的变化，所以动态添加的<code>name</code>属性并不是响应式的。</p>
<p>如果我们这里有这个需求，可以通过<code>vm.$set</code>或者是<code>Vue.set</code>来解决(这两个方法是一样的)。</p>
<p>如下代码：</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vm.$set(vm.obj,&#x27;name&#x27;,&#x27;zhangsan&#x27;)</span><br></pre></td></tr></table></figure>

<p>上面的代码，通过<code>Vue</code>的实例调用了<code>$set</code>方法，给<code>obj</code>对象添加了<code>name</code>属性，值为<code>zhangsan</code>.</p>
<p>通过以上方式添加属性为响应式的。</p>
<p>下面我们修改一下<code>arr</code>数组中的第一项内容，看一下是否为响应式的。</p>
<p>如果是<code>arr[0]=100</code>这种修改方式，并不是响应式的，关于这一点我们在前面也讲解过。</p>
<p>我么可以使用<code>slice</code>函数来修改，或者可以使用<code>vm.$set</code>方法也是可以的。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vm.$set(vm.arr,0,100)</span><br></pre></td></tr></table></figure>

<p>把<code>arr</code>数组中的下标为<code>0</code>的这一项的值修改为<code>100</code>&#x2F;</p>
<p>以上代码实现的操作为响应式的。</p>
<p>关于<code>vm.$set</code>的使用也可以参考官方文档。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">https://cn.vuejs.org/v2/api/#Vue-set</span><br></pre></td></tr></table></figure>

<p>向响应式对象中添加一个 <code>property</code>，并确保这个新 <code>property </code>同样是响应式的，且触发视图更新。它必须用于向响应式对象上添加新 <code>property</code>，因为 <code>Vue</code>无法探测普通的新增<code> property</code> (比如 <code>this.myObject.newProperty = &#39;hi&#39;</code>)</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">注意对象不能是 Vue 实例，或者 Vue 实例的根数据对象</span><br></pre></td></tr></table></figure>

<p>以下代码是错误的。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vm.$set(vm, <span class="string">&quot;abc&quot;</span>, <span class="string">&quot;a&quot;</span>);</span><br></pre></td></tr></table></figure>

<p>以上代码是想<code>vue</code>的实例添加属性<code>abc</code>,以上写法是错误的。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vm.$set(vm.<span class="property">$data</span>, <span class="string">&quot;abc&quot;</span>, <span class="string">&quot;12&quot;</span>);</span><br></pre></td></tr></table></figure>

<p>以上代码是向<code>data</code>中添加属性，以上代码也是错误的。表明不能向<code>Vue</code>实例的根数据对象中动态添加属性。</p>
<p>以上就是关于<code>set</code>方法的使用的方式。下一小节，我们来查看<code>set</code>的源代码。</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="http://example.com">Justic</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="http://example.com/2022/07/15/Vue/Vue%20%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/">http://example.com/2022/07/15/Vue/Vue 源码解读/Vue源码解读/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://justicc.top/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="http://example.com" target="_blank">Justic</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Vue/">Vue</a></div><div class="post_share"><div class="social-share" data-image="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/j3/wallhaven-j3qq15.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><div class="post-reward"><div class="reward-button"><i class="fas fa-qrcode"></i> 打赏</div><div class="reward-main"><ul class="reward-all"><li class="reward-item"><a href="/img/wechat.jpg" target="_blank"><img class="post-qr-code-img" src= "" data-lazy-src="/img/wechat.jpg" alt="微信"/></a><div class="post-qr-code-desc">微信</div></li><li class="reward-item"><a href="/img/alipay.jpg" target="_blank"><img class="post-qr-code-img" src= "" data-lazy-src="/img/alipay.jpg" alt="支付宝"/></a><div class="post-qr-code-desc">支付宝</div></li></ul></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/07/15/Vue/Vue%20%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%B8%B2%E6%9F%93/Vue%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93/"><img class="prev-cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/ne/wallhaven-ne8mzl.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Vue 服务器渲染</div></div></a></div><div class="next-post pull-right"><a href="/2022/07/15/Vue/Vue%20%E8%99%9A%E6%8B%9Fdom/Vue%20%E8%99%9A%E6%8B%9Fdom/"><img class="next-cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/k7/wallhaven-k7jmg6.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Vue 虚拟DOM</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/07/13/Vue/Vue%20Router/vueRouter/" title="Vue 路由"><img class="cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/ne/wallhaven-ne8mzl.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-07-13</div><div class="title">Vue 路由</div></div></a></div><div><a href="/2022/07/13/Vue/Vue%20%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86/Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86/" title="Vue 响应式原理"><img class="cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/j3/wallhaven-j3qq15.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-07-13</div><div class="title">Vue 响应式原理</div></div></a></div><div><a href="/2022/10/20/Vue/Vue%20%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A7%A3%E6%9E%90/Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A7%A3%E6%9E%90/" title="Vue 响应式解析"><img class="cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/z8/wallhaven-z82evv.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-10-20</div><div class="title">Vue 响应式解析</div></div></a></div><div><a href="/2022/07/15/Vue/Vue%20%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%B8%B2%E6%9F%93/Vue%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93/" title="Vue 服务器渲染"><img class="cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/ne/wallhaven-ne8mzl.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-07-15</div><div class="title">Vue 服务器渲染</div></div></a></div><div><a href="/2022/07/15/Vue/Vue%20%E8%99%9A%E6%8B%9Fdom/Vue%20%E8%99%9A%E6%8B%9Fdom/" title="Vue 虚拟DOM"><img class="cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/k7/wallhaven-k7jmg6.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-07-15</div><div class="title">Vue 虚拟DOM</div></div></a></div><div><a href="/2022/07/15/Vue/Vue%20%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98/Vue%E9%9D%A2%E8%AF%95%E9%A2%98/" title="Vue 面试题"><img class="cover" src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/yj/wallhaven-yj1kgl.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-07-15</div><div class="title">Vue 面试题</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div class="vcomment" id="vcomment"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src= "" data-lazy-src="https://portrait.gitee.com/uploads/avatars/user/2982/8948190_JIAXInT_1624177607.png!avatar200" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">Justic</div><div class="author-info__description">最是人间留不住 朱颜辞镜花辞树</div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">22</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">2</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">3</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E4%B8%80%E3%80%81Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E2%80%93%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86"><span class="toc-text">一、Vue源码解析–响应式原理</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#1%E3%80%81%E8%AF%BE%E7%A8%8B%E7%9B%AE%E6%A0%87"><span class="toc-text">1、课程目标</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2%E3%80%81%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C"><span class="toc-text">2、准备工作</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3%E3%80%81%E6%89%93%E5%8C%85"><span class="toc-text">3、打包</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#4%E3%80%81Vue-%E4%B8%8D%E5%90%8C%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E"><span class="toc-text">4、Vue 不同版本说明</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#5%E3%80%81%E5%AF%BB%E6%89%BE%E5%85%A5%E5%8F%A3%E6%96%87%E4%BB%B6"><span class="toc-text">5、寻找入口文件</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#6%E3%80%81%E4%BB%8E%E5%85%A5%E5%8F%A3%E5%BC%80%E5%A7%8B"><span class="toc-text">6、从入口开始</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#7%E3%80%81Vue-%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E8%BF%87%E7%A8%8B"><span class="toc-text">7、Vue 的初始化过程</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#8%E3%80%81%E9%9D%99%E6%80%81%E6%88%90%E5%91%98%E5%88%9D%E5%A7%8B%E5%8C%96"><span class="toc-text">8、静态成员初始化</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#9%E3%80%81Vue-%E5%AE%9E%E4%BE%8B%E6%88%90%E5%91%98%E5%88%9D%E5%A7%8B%E5%8C%96"><span class="toc-text">9、Vue 实例成员初始化</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#10%E3%80%81init-%E6%96%B9%E6%B3%95"><span class="toc-text">10、init 方法</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#11%E3%80%81initState-%E6%96%B9%E6%B3%95"><span class="toc-text">11、initState 方法</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#12%E3%80%81%E6%80%BB%E7%BB%93"><span class="toc-text">12、总结</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#13%E3%80%81%E5%93%8D%E5%BA%94%E5%BC%8F%E5%A4%84%E7%90%86%E5%85%A5%E5%8F%A3"><span class="toc-text">13、响应式处理入口</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#14%E3%80%81Observer"><span class="toc-text">14、Observer</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#15%E3%80%81defineReactive"><span class="toc-text">15、defineReactive</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#16%E3%80%81%E4%BE%9D%E8%B5%96%E6%94%B6%E9%9B%86"><span class="toc-text">16、依赖收集</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#17%E3%80%81%E6%95%B0%E7%BB%84%E7%9A%84%E5%93%8D%E5%BA%94%E5%BC%8F%E5%A4%84%E7%90%86"><span class="toc-text">17、数组的响应式处理</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#18%E3%80%81%E6%95%B0%E7%BB%84%E7%BB%83%E4%B9%A0"><span class="toc-text">18、数组练习</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#19%E3%80%81Watcher"><span class="toc-text">19、Watcher</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#20%E3%80%81%E6%80%BB%E7%BB%93%E5%93%8D%E5%BA%94%E5%BC%8F%E5%A4%84%E7%90%86%E7%9A%84%E8%BF%87%E7%A8%8B"><span class="toc-text">20、总结响应式处理的过程</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#21%E3%80%81%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E5%93%8D%E5%BA%94%E5%BC%8F%E5%B1%9E%E6%80%A7"><span class="toc-text">21、动态添加一个响应式属性</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/2022/11/09/JavaScript/ES6%E6%96%B0%E7%89%B9%E6%80%A7/index/" title="ES6新特性"><img src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/m9/wallhaven-m9y289.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="ES6新特性"/></a><div class="content"><a class="title" href="/2022/11/09/JavaScript/ES6%E6%96%B0%E7%89%B9%E6%80%A7/index/" title="ES6新特性">ES6新特性</a><time datetime="2022-11-09T12:55:33.000Z" title="发表于 2022-11-09 20:55:33">2022-11-09</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/10/20/Vue/Vue%20%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A7%A3%E6%9E%90/Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A7%A3%E6%9E%90/" title="Vue 响应式解析"><img src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/z8/wallhaven-z82evv.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Vue 响应式解析"/></a><div class="content"><a class="title" href="/2022/10/20/Vue/Vue%20%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A7%A3%E6%9E%90/Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A7%A3%E6%9E%90/" title="Vue 响应式解析">Vue 响应式解析</a><time datetime="2022-10-20T12:05:37.000Z" title="发表于 2022-10-20 20:05:37">2022-10-20</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/08/08/JavaScript/JavaScript%E5%8E%9F%E5%9E%8B%E5%92%8C%E5%8E%9F%E5%9E%8B%E9%93%BE/index/" title="JavaScript原型和原型链"><img src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/z8/wallhaven-z82evv.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="JavaScript原型和原型链"/></a><div class="content"><a class="title" href="/2022/08/08/JavaScript/JavaScript%E5%8E%9F%E5%9E%8B%E5%92%8C%E5%8E%9F%E5%9E%8B%E9%93%BE/index/" title="JavaScript原型和原型链">JavaScript原型和原型链</a><time datetime="2022-08-08T09:08:31.000Z" title="发表于 2022-08-08 17:08:31">2022-08-08</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/08/08/JavaScript/JavaScript%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%A2%9E%E5%BC%BA%E7%9F%A5%E8%AF%86/index/" title="JavaScript对象的增强知识"><img src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/j3/wallhaven-j3qq15.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="JavaScript对象的增强知识"/></a><div class="content"><a class="title" href="/2022/08/08/JavaScript/JavaScript%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%A2%9E%E5%BC%BA%E7%9F%A5%E8%AF%86/index/" title="JavaScript对象的增强知识">JavaScript对象的增强知识</a><time datetime="2022-08-08T07:43:36.000Z" title="发表于 2022-08-08 15:43:36">2022-08-08</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/08/08/JavaScript/JavaScript%E5%87%BD%E6%95%B0%E5%A2%9E%E5%BC%BA%E7%9F%A5%E8%AF%86/index/" title="JavaScript函数增强知识"><img src= "" data-lazy-src="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/28/wallhaven-2881yx.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="JavaScript函数增强知识"/></a><div class="content"><a class="title" href="/2022/08/08/JavaScript/JavaScript%E5%87%BD%E6%95%B0%E5%A2%9E%E5%BC%BA%E7%9F%A5%E8%AF%86/index/" title="JavaScript函数增强知识">JavaScript函数增强知识</a><time datetime="2022-08-08T03:25:40.000Z" title="发表于 2022-08-08 11:25:40">2022-08-08</time></div></div></div></div></div></div></main><footer id="footer" style="background-image: url('https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/j3/wallhaven-j3qq15.jpg')"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By Justic</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a><img src= "" data-lazy-src="https://static.dy208.cn/o_1dfilp8ruo521thr1hvf18ji17soa.png">
<a href="http://www.beian.miit.gov.cn/"  style="color:#f72b07" target="_blank">晋ICP备2021014440号-2</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="chat_btn" type="button" title="聊天"><i class="fas fa-sms"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"><script>function loadValine () {
  function initValine () {
    const valine = new Valine(Object.assign({
      el: '#vcomment',
      appId: 'KgIigedIwh0GobYsWWfPMEdO-gzGzoHsz',
      appKey: 'WpJEQP22asTau19i2tq1yQhU',
      avatar: 'justic',
      serverURLs: '',
      emojiMaps: "",
      path: window.location.pathname,
      visitor: false
    }, null))
  }

  if (typeof Valine === 'function') initValine() 
  else getScript('https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js').then(initValine)
}

if ('Valine' === 'Valine' || !false) {
  if (false) btf.loadComment(document.getElementById('vcomment'),loadValine)
  else setTimeout(loadValine, 0)
} else {
  function loadOtherComment () {
    loadValine()
  }
}</script></div><div class="aplayer no-destroy" data-id="5076309057" data-server="netease" data-type="playlist" data-fixed="true" data-autoplay="true" data-lrcType="-1"> </div><script id="canvas_nest" defer="defer" color="0,0,255" opacity="0.7" zIndex="-1" count="99" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-nest.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/activate-power-mode.min.js"></script><script>POWERMODE.colorful = true;
POWERMODE.shake = false;
POWERMODE.mobile = true;
document.body.addEventListener('input', POWERMODE);
</script><script id="click-show-text" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/click-show-text.min.js" data-mobile="true" data-text="富强,民主,文明,和谐,平等,公正,法治,爱国,敬业,诚信,友善" data-fontsize="15px" data-random="true" async="async"></script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/metingjs/dist/Meting.min.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div><!-- hexo injector body_end start --><script data-pjax>
  function butterfly_clock_anzhiyu_injector_config(){
    var parent_div_git = document.getElementsByClassName('sticky_layout')[0];
    var item_html = '<div class="card-widget card-clock"><div class="card-glass"><div class="card-background"><div class="card-content"><div id="hexo_electric_clock"><img class="entered loading" id="card-clock-loading" src= "" data-lazy-src="https://cdn.cbd.int/hexo-butterfly-clock-anzhiyu/lib/loading.gif" style="height: 120px; width: 100%;" data-ll-status="loading"/></div></div></div></div></div>';
    console.log('已挂载butterfly_clock_anzhiyu')
    parent_div_git.insertAdjacentHTML("afterbegin",item_html)
    }
  var elist = 'null'.split(',');
  var cpage = location.pathname;
  var epage = 'all';
  var qweather_key = 'f854dfdd82d747fca7fd04f092861e87';
  var gaud_map_key = '427416c518f4476a01117be40057a892';
  var baidu_ak_key = 'undefined';
  var flag = 0;
  var clock_rectangle = '112.982279,28.19409';
  var clock_default_rectangle_enable = 'false';

  for (var i=0;i<elist.length;i++){
    if (cpage.includes(elist[i])){
      flag++;
    }
  }

  if ((epage ==='all')&&(flag == 0)){
    butterfly_clock_anzhiyu_injector_config();
  }
  else if (epage === cpage){
    butterfly_clock_anzhiyu_injector_config();
  }
  </script><script src="https://widget.qweather.net/simple/static/js/he-simple-common.js?v=2.0"></script><script data-pjax src="https://cdn.cbd.int/hexo-butterfly-clock-anzhiyu/lib/clock.min.js"></script><!-- hexo injector body_end end --></body></html>